Some odd tweaks and repo management code

This commit is contained in:
Ellwood Zwovic
2017-07-18 15:10:11 -07:00
parent c0d2419bea
commit 6193437636
2 changed files with 118 additions and 21 deletions

View File

@@ -126,7 +126,7 @@ class SubprocMixin:
def handle_received_data(self):
recvd = self.pipe_blender.recv()
self.log.info('Received message from subprocess: %s', recvd)
self.log.debug('Received message from subprocess: %s', recvd)
try:
handler = self.msg_handlers[type(recvd)]
except KeyError:
@@ -261,7 +261,7 @@ class BPKG_OT_refresh(SubprocMixin, bpy.types.Operator):
import pathlib
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'addons', create=True))
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'packages', create=True))
repository_url = bpy.context.user_preferences.addons[__package__].preferences.repository_url
proc = multiprocessing.Process(target=subproc.refresh,
@@ -326,6 +326,68 @@ class BPKG_OT_hang(SubprocMixin, bpy.types.Operator):
def report_process_died(self):
self.report({'ERROR'}, 'Process died, exit code %s' % self.process.exitcode)
class BPKG_OT_load_repositories(SubprocMixin, bpy.types.Operator):
bl_idname = 'bpkg.load_repositories'
bl_label = 'Load Repositories'
bl_description = 'Load repositories from disk'
bl_options = {'REGISTER'}
log = logging.getLogger(__name__ + '.BPKG_OT_load_repositories')
def create_subprocess(self):
"""
Start the load process and register message handlers
"""
import multiprocessing
import pathlib
# TODO: We need other paths besides this one on subprocess end, so it might be better to pass them all at once.
# For now, just pass this one.
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'packages', create=True))
self.log.debug("Using %s as install path", install_path)
import addon_utils
proc = multiprocessing.Process(
target=subproc.load_repositories,
args=(self.pipe_subproc, self.storage_path)
)
return proc
self.msg_handlers = {
subproc.SubprocError: self._subproc_error,
subproc.RepositoryResult: self._subproc_repository_result,
subproc.Success: self._subproc_success,
subproc.Aborted: self._subproc_aborted,
}
def _subproc_error(self, error: subproc.SubprocError):
self.report({'ERROR'}, 'Failed to load repositories: %s' % error.message)
self.quit()
def _subproc_repository_result(self, result: subproc.RepositoryResult):
bpy.context.user_preferences.addons[__package__].preferences['repo'] = result.repository
self.log.info("Loaded repository %s", result.repository.name)
def _subproc_success(self, success: subproc.Success):
self.log.info("Successfully loaded repositories")
self.quit()
def _subproc_aborted(self, aborted: subproc.Aborted):
self.report({'ERROR'}, 'Package installation aborted per your request')
self.quit()
def report_process_died(self):
if self.process.exitcode:
self.log.error('Process died without telling us! Exit code was %i', self.process.exitcode)
self.report({'ERROR'}, 'Error downloading package, exit code %i' % self.process.exitcode)
else:
self.log.error('Process died without telling us! Exit code was 0 though')
self.report({'WARNING'}, 'Error downloading package, but process finished OK. This is weird.')
class USERPREF_PT_packages(bpy.types.Panel):
bl_label = "Package Management"
bl_space_type = 'USER_PREFERENCES'
@@ -340,12 +402,6 @@ class USERPREF_PT_packages(bpy.types.Panel):
return (userpref.active_section == 'PACKAGES')
def draw(self, context):
try:
repo = context.user_preferences.addons[__package__].preferences['repo']
except KeyError:
# HACK:
# If no repositories are initialized, we should try to refresh them. If that doesn't work, display a message
repo = {'packages': []}
layout = self.layout
@@ -365,7 +421,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
#TODO: more advanced filter/sorting; sort matches which match the filter string from the start higher
#Also some caching of this would be nice, this only needs to be re-run when any of the filters change.
def filter_package(package):
def filter_package(package):# {{{
"""Returns true if the given package matches all filters"""
filterstr = bpy.context.window_manager.package_search
category = bpy.context.window_manager.addon_filter
@@ -390,9 +446,9 @@ class USERPREF_PT_packages(bpy.types.Panel):
if match_search() and match_category():
return True
return False
return False# }}}
def draw_package(pkg, layout):
def draw_package(pkg, layout):# {{{
"""Draws the given package"""
pkgbox = layout.box()
spl = pkgbox.split(.8)
@@ -461,12 +517,35 @@ class USERPREF_PT_packages(bpy.types.Panel):
spl.label("{}:".format(prop.title()))
spl.label(str(blinfo[prop]))
if pkg.get('expand'):
expanded()
else:
collapsed()
collapsed()# }}}
def center_message(layout, msg: str):
"""draw a label in the center of an extra-tall row"""
row = layout.row()
row.label(text=msg)
row.alignment='CENTER'
row.scale_y = 10
try:
#TODO either store repos in windowmanager and reload from disk every time, or store them in the .blend. Not both
repo = context.user_preferences.addons[__package__].preferences['repo']
except KeyError:
center_message("Loading Repositories...")
import pathlib
# TODO: read repository synchronously for now; can't run an operator to do async monitoring from draw code
prefs = bpy.context.user_preferences.addons[__package__].preferences
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'packages', create=True))
res = subproc._load_repo(storage_path)
prefs['repo'] = res.to_dict(sort=True, ids=True)
return
if repo is None:
center_message("No repository found. Add one in the addon preferences.")
return
for pkg in repo['packages']:
@@ -527,6 +606,7 @@ class PackageManagerPreferences(bpy.types.AddonPreferences):
def register():
bpy.utils.register_class(BPKG_OT_install)
bpy.utils.register_class(BPKG_OT_refresh)
bpy.utils.register_class(BPKG_OT_load_repositories)
bpy.utils.register_class(BPKG_OT_hang)
bpy.utils.register_class(USERPREF_PT_packages)
bpy.utils.register_class(WM_OT_package_toggle_expand)
@@ -549,6 +629,7 @@ def register():
def unregister():
bpy.utils.unregister_class(BPKG_OT_install)
bpy.utils.unregister_class(BPKG_OT_refresh)
bpy.utils.unregister_class(BPKG_OT_load_repositories)
bpy.utils.unregister_class(BPKG_OT_hang)
bpy.utils.unregister_class(USERPREF_PT_packages)
bpy.utils.unregister_class(WM_OT_package_toggle_expand)

View File

@@ -178,8 +178,6 @@ class Repository:
if url is None:
url = ""
self.set_from_dict({'url': url})
self.log.debug("Initializing repository: %s", self.to_dict())
self.log.debug("Own URL is '%s'", self.url)
# def cleanse_packagelist(self):
# """Remove empty packages (no bl_info), packages with no name"""
@@ -255,7 +253,6 @@ class Repository:
if ids:
for pkg in packages:
self.log.debug(pkg['url'], pkg['bl_info']['name'], self.name, self.url)
# hash may be too big for a C int
pkg['id'] = str(hash(pkg['url'] + pkg['bl_info']['name'] + self.name + self.url))
@@ -389,6 +386,22 @@ def _download(pipe_to_blender, package_url: str, download_dir: pathlib.Path) ->
return local_fpath
def _add_to_installed(storage_path: pathlib.Path, pkg: Package):
"""Add pkg to local repository"""
repo_path = storage_path / 'local.json'
if repo_path.exists():
repo = Repository.from_file(repo_path)
else:
repo = Repository()
repo.packages.append(pkg)
repo.to_file(repo_path)
def _remove_from_installed(storage_path: pathlib.Path, pkg: Package):
"""Remove pkg from local repository"""
repo = Repository.from_file(storage_path / 'local.json')
#TODO: this won't work, compare by name? (watch out for conflicts though)
repo.packages.remove(pkg)
def _install(pipe_to_blender, pkgpath: pathlib.Path, dest: pathlib.Path, searchpaths: list):
"""Extracts/moves package at `pkgpath` to `dest`"""
import zipfile
@@ -466,7 +479,7 @@ def _install(pipe_to_blender, pkgpath: pathlib.Path, dest: pathlib.Path, searchp
return
def download_and_install(pipe_to_blender, package_url: str, install_path: pathlib.Path, search_paths: list):
def download_and_install(pipe_to_blender, package: dict, install_path: pathlib.Path, repo_path: pathlib.Path, search_paths: list):
"""Downloads and installs the given package."""
from . import cache
@@ -474,16 +487,15 @@ def download_and_install(pipe_to_blender, package_url: str, install_path: pathli
log = logging.getLogger('%s.download_and_install' % __name__)
cache_dir = cache.cache_directory('downloads')
downloaded = _download(pipe_to_blender, package_url, cache_dir)
downloaded = _download(pipe_to_blender, package.url, cache_dir)
if not downloaded:
log.debug('Download failed/aborted, not going to install anything.')
return
# Only send success if _install doesn't throw an exception
# Maybe not the best way to do this? (will also catch exceptions which occur during message sending)
try:
_install(pipe_to_blender, downloaded, install_path, search_paths)
_add_to_installed(repo_path, Package.from_dict(package))
pipe_to_blender.send(Success())
except InstallException as err:
log.exception("Failed to install package: %s", err)
@@ -526,9 +538,13 @@ def load(pipe_to_blender, storage_path: pathlib.Path):
try:
repo = _load_repo(storage_path)
pipe_to_blender.send(RepositoryResult(repo.to_dict(sort=True, ids=True)))
pipe_to_blender.send(Success())
return repo
except BadRepository as err:
pipe_to_blender.send(SubprocError("Failed to read repository: %s" % err))
# def load_local(pipe_to_blender
def debug_hang():
"""Hangs for an hour. For testing purposes only."""