[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