[Bf-blender-cvs] [0ff4627729c] soc-2017-package_manager: Move package manager code out of addon

gandalf3 noreply at git.blender.org
Mon Aug 28 09:46:54 CEST 2017


Commit: 0ff4627729cbcaaaf9c0545792ace21e35736cb5
Author: gandalf3
Date:   Sat Aug 26 02:07:18 2017 -0700
Branches: soc-2017-package_manager
https://developer.blender.org/rB0ff4627729cbcaaaf9c0545792ace21e35736cb5

Move package manager code out of addon

Code from addon repository: https://developer.blender.org/diffusion/BPMA/

===================================================================

A	release/scripts/modules/bpkg/__init__.py
A	release/scripts/modules/bpkg/exceptions.py
A	release/scripts/modules/bpkg/messages.py
A	release/scripts/modules/bpkg/subproc.py
A	release/scripts/modules/bpkg/types.py
A	release/scripts/modules/bpkg/utils.py
M	release/scripts/startup/bl_operators/__init__.py
A	release/scripts/startup/bl_operators/package.py
M	release/scripts/startup/bl_ui/__init__.py
A	release/scripts/startup/bl_ui/properties_package.py
M	release/scripts/startup/bl_ui/space_userpref.py

===================================================================

diff --git a/release/scripts/modules/bpkg/__init__.py b/release/scripts/modules/bpkg/__init__.py
new file mode 100644
index 00000000000..01fe1a422a2
--- /dev/null
+++ b/release/scripts/modules/bpkg/__init__.py
@@ -0,0 +1,71 @@
+# __all__ = (
+#     "exceptions",
+#     "types",
+# )
+
+from . import utils
+from .types import (
+    Package,
+    ConsolidatedPackage,
+    Repository,
+    )
+from pathlib import Path
+from collections import OrderedDict
+import logging
+import bpy
+
+packages = {}
+
+def get_installed_packages(refresh=False) -> list:
+    """Get list of packages installed on disk"""
+    import addon_utils
+    installed_pkgs = []
+    for mod in addon_utils.modules(refresh=refresh):
+        pkg = Package.from_module(mod)
+        pkg.installed = True
+        installed_pkgs.append(pkg)
+    return installed_pkgs
+
+def get_repo_storage_path() -> Path:
+    return Path(bpy.utils.user_resource('CONFIG', 'repositories'))
+
+def get_repositories() -> list:
+    """
+    Get list of downloaded repositories and update wm.package_repositories
+    """
+    log = logging.getLogger(__name__ + ".get_repositories")
+    storage_path = get_repo_storage_path()
+    repos = utils.load_repositories(storage_path)
+    log.debug("repos: %s", repos)
+
+    return repos
+
+
+def list_packages() -> OrderedDict: # {{{
+    """Make an OrderedDict of ConsolidatedPackages from known repositories +
+    installed packages, keyed by package name"""
+
+    # log = logging.getLogger(__name__ + ".build_composite_packagelist")
+    masterlist = {}
+    installed_packages = get_installed_packages(refresh=True)
+    known_repositories = get_repositories()
+
+    for repo in known_repositories:
+        for pkg in repo.packages:
+            pkg.repositories.add(repo)
+            if pkg.name is None:
+                return OrderedDict()
+            if pkg.name in masterlist:
+                masterlist[pkg.name].add_version(pkg)
+            else:
+                masterlist[pkg.name] = ConsolidatedPackage(pkg)
+
+    for pkg in installed_packages:
+        if pkg.name in masterlist:
+            masterlist[pkg.name].add_version(pkg)
+        else:
+            masterlist[pkg.name] = ConsolidatedPackage(pkg)
+
+    # log.debug(masterlist[None].__dict__)
+    return OrderedDict(sorted(masterlist.items()))
+# }}}
diff --git a/release/scripts/modules/bpkg/exceptions.py b/release/scripts/modules/bpkg/exceptions.py
new file mode 100644
index 00000000000..0e9d3395ed1
--- /dev/null
+++ b/release/scripts/modules/bpkg/exceptions.py
@@ -0,0 +1,14 @@
+class BpkgException(Exception):
+    """Superclass for all package manager exceptions"""
+
+class InstallException(BpkgException):
+    """Raised when there is an error during installation"""
+
+class DownloadException(BpkgException):
+    """Raised when there is an error downloading something"""
+
+class BadRepositoryException(BpkgException):
+    """Raised when there is an error while reading or manipulating a repository"""
+
+class PackageException(BpkgException):
+    """Raised when there is an error while manipulating a package"""
diff --git a/release/scripts/modules/bpkg/messages.py b/release/scripts/modules/bpkg/messages.py
new file mode 100644
index 00000000000..ce754d49811
--- /dev/null
+++ b/release/scripts/modules/bpkg/messages.py
@@ -0,0 +1,73 @@
+from .types import Repository
+
+class Message:
+    """Superclass for all message sent over pipes."""
+
+
+# Blender messages
+
+class BlenderMessage(Message):
+    """Superclass for all messages sent from Blender to the subprocess."""
+
+class Abort(BlenderMessage):
+    """Sent when the user requests abortion of a task."""
+
+
+# Subproc messages
+
+class SubprocMessage(Message):
+    """Superclass for all messages sent from the subprocess to Blender."""
+
+class Progress(SubprocMessage):
+    """Send from subprocess to Blender to report progress.
+
+    :ivar progress: the progress percentage, from 0-1.
+    """
+
+    def __init__(self, progress: float):
+        self.progress = progress
+
+class Success(SubprocMessage):
+    """Sent when an operation finished sucessfully."""
+
+class RepositoryResult(SubprocMessage):
+    """Sent when an operation returns a repository to be used on the parent process."""
+
+    def __init__(self, repository_name: str):
+        self.repository = repository
+
+class Aborted(SubprocMessage):
+    """Sent as response to Abort message."""
+
+# subproc warnings
+
+class SubprocWarning(SubprocMessage):
+    """Superclass for all non-fatal warning messages sent from the subprocess."""
+
+    def __init__(self, message: str):
+        self.message = message
+
+# subproc errors
+
+class SubprocError(SubprocMessage):
+    """Superclass for all fatal error messages sent from the subprocess."""
+
+    def __init__(self, message: str):
+        self.message = message
+
+class InstallError(SubprocError):
+    """Sent when there was an error installing something."""
+
+class UninstallError(SubprocError):
+    """Sent when there was an error uninstalling something."""
+
+class BadRepositoryError(SubprocError):
+    """Sent when a repository can't be used for some reason"""
+
+class DownloadError(SubprocMessage):
+    """Sent when there was an error downloading something."""
+
+    def __init__(self, message: str, status_code: int = None):
+        self.status_code = status_code
+        self.message = message
+
diff --git a/release/scripts/modules/bpkg/subproc.py b/release/scripts/modules/bpkg/subproc.py
new file mode 100644
index 00000000000..aa249517d61
--- /dev/null
+++ b/release/scripts/modules/bpkg/subproc.py
@@ -0,0 +1,85 @@
+"""
+All the stuff that needs to run in a subprocess.
+"""
+
+from pathlib import Path
+from . import (
+    messages,
+    exceptions, 
+    utils,
+)
+from .types import (
+    Package,
+    Repository,
+)
+import logging
+
+def download_and_install_package(pipe_to_blender, package: Package, install_path: Path):
+    """Downloads and installs the given package."""
+
+    log = logging.getLogger(__name__ + '.download_and_install')
+
+    from . import cache
+    cache_dir = cache.cache_directory('downloads')
+
+    try:
+        package.install(install_path, cache_dir)
+    except exceptions.DownloadException as err:
+        pipe_to_blender.send(messages.DownloadError(err))
+        log.exception(err)
+    except exceptions.InstallException as err:
+        pipe_to_blender.send(messages.InstallError(err))
+        log.exception(err)
+
+    pipe_to_blender.send(messages.Success())
+
+
+def uninstall_package(pipe_to_blender, package: Package, install_path: Path):
+    """Deletes the given package's files from the install directory"""
+    #TODO: move package to cache and present an "undo" button to user, to give nicer UX on misclicks
+
+    for pkgfile in [install_path / Path(p) for p in package.files]:
+        if not pkgfile.exists():
+            pipe_to_blender.send(messages.UninstallError("Could not find file owned by package: '%s'. Refusing to uninstall." % pkgfile))
+            return None
+
+    for pkgfile in [install_path / Path(p) for p in package.files]:
+        utils.rm(pkgfile)
+
+    pipe_to_blender.send(messages.Success())
+
+
+def refresh_repositories(pipe_to_blender, repo_storage_path: Path, repository_urls: str, progress_callback=None):
+    """Downloads and stores the given repository"""
+
+    log = logging.getLogger(__name__ + '.refresh_repository')
+
+    if progress_callback is None:
+        progress_callback = lambda x: None
+    progress_callback(0.0)
+
+    repos = utils.load_repositories(repo_storage_path)
+
+    def prog(progress: float):
+        progress_callback(progress/len(repos))
+
+    known_repo_urls = [repo.url for repo in repos]
+    for repo_url in repository_urls:
+        if repo_url not in known_repo_urls:
+            repos.append(Repository(repo_url))
+
+    for repo in repos:
+        log.debug("repo name: %s, url: %s", repo.name, repo.url)
+    for repo in repos:
+        try:
+            repo.refresh(repo_storage_path, progress_callback=prog)
+        except exceptions.DownloadException as err:
+            pipe_to_blender.send(messages.DownloadError(err))
+            log.exception("Download error")
+        except exceptions.BadRepositoryException as err:
+            pipe_to_blender.send(messages.BadRepositoryError(err))
+            log.exception("Bad repository")
+
+    progress_callback(1.0)
+    pipe_to_blender.send(messages.Success())
+
diff --git a/release/scripts/modules/bpkg/types.py b/release/scripts/modules/bpkg/types.py
new file mode 100644
index 00000000000..7ed99b2d997
--- /dev/null
+++ b/release/scripts/modules/bpkg/types.py
@@ -0,0 +1,501 @@
+import logging
+import json
+from pathlib import Path
+from . import exceptions
+from . import utils
+
+class Package:
+    """
+    Stores package methods and metadata
+    """
+
+    log = logging.getLogger(__name__ + ".Package")
+
+    def __init__(self, package_dict:dict = None):
+        self.bl_info = {}
+        self.url     = ""
+        self.files   = []
+
+        self.repositories = set()
+        self.installed_location = None
+        self.module_name = None
+
+        self.installed = False
+        self.is_user = False
+        self.enabled = False
+
+        self.set_from_dict(package_dict)
+
+    def test_is_user(self) -> bool:
+        """Return true if package's install location is in user or preferences scripts path"""
+        import bpy
+        user_script_path = bpy.utils.script_path_user()
+        prefs_script_path = bpy.utils.script_path_pref()
+
+        if user_script_path is not None:
+            in_user = Path(user_script_path) in Path(self.installed_location).parents
+        else:
+            in_user = False
+
+        if prefs_script_path is not None:
+            in_prefs = Path(prefs_script_path) in Path(self.installed_location).parents
+        else:
+            in_prefs = False
+
+        return in_user or in_prefs
+
+    def test_enabled(self) -> bool:
+        """Return true if package is enabled"""
+        import bpy
+        if self.module_name is not None:
+            return (self.module_name in bpy.context.user_preferences.addons)
+        else:
+            r

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list