diff --git a/__init__.py b/__init__.py index 9fb5e1c..ec0fd92 100644 --- a/__init__.py +++ b/__init__.py @@ -34,6 +34,8 @@ def register(): return new_module blender_common = reload_mod('blender_common') + subprocess_adapter = reload_mod('subprocess_adapter') + bpackage = reload_mod('bpackage') else: from . import blender_common diff --git a/blender_common.py b/blender_common.py index 23aa3c0..6ef87ba 100644 --- a/blender_common.py +++ b/blender_common.py @@ -5,7 +5,7 @@ import logging import bpy from bpy.props import CollectionProperty from bpy.types import PropertyGroup, Panel, UIList, AddonPreferences, Operator -from .subprocess_adapter import subprocess_operator +from .subprocess_adapter import subprocess_operator, subprocess_function from . import bpackage as bpkg class RepositoryProperty(PropertyGroup): @@ -42,27 +42,30 @@ class PackagePreferences(AddonPreferences): @subprocess_operator class PACKAGE_OT_refresh(Operator): + """ + Operator which checks for updates to known package lists + """ bl_idname = "package.refresh" - bl_label = "Update package list(s)" + bl_label = "Refresh package list(s)" log = logging.getLogger(__name__) def invoke(self, context, event): prefs = context.user_preferences.addons[__package__].preferences + if 'repositories' not in prefs: return {'FINISHED'} + # HACK: just do the active repo for now repo = bpkg.Repository(prefs['repositories'][prefs.active_repository].to_dict()) - self.proc_args = [] - self.proc_kwargs = {'target': repo.refresh} - - def execute(self, context, event): - pass + self.proc_kwargs = {'target': subprocess_function(repo.refresh, pipe=self.pipe)} + def modal(self, context, event): # try: self.poll_subprocess() # except: + return {'RUNNING_MODAL'} def handle_response(self, resp): self.report({'INFO'}, "Request returned %s" % resp) diff --git a/subprocess_adapter.py b/subprocess_adapter.py index 904d4ec..37724e5 100644 --- a/subprocess_adapter.py +++ b/subprocess_adapter.py @@ -2,6 +2,8 @@ import logging from multiprocessing import Process, Pipe from bpy.types import Operator +log = logging.getLogger(__name__) + def subprocess_operator(cls: Operator, polling_interval=.01) -> Operator: """ Class decorator which wraps Operator methods with setup code for running a subprocess. @@ -34,9 +36,10 @@ def subprocess_operator(cls: Operator, polling_interval=.01) -> Operator: def decorate_invoke(orig_invoke): def invoke(self, context, event): + self.pipe = Pipe() + orig_invoke(self, context, event) - self.pipe = Pipe() self.proc = Process( *getattr(self, 'proc_args', []), **getattr(self, 'proc_kwargs', []) @@ -57,15 +60,19 @@ def subprocess_operator(cls: Operator, polling_interval=.01) -> Operator: self.log.debug("polling") try: if self.pipe[0].poll(): - newdata = self.pipe[0].recv() + resp = self.pipe[0].recv() + assert(isinstance(resp, SubprocessMessage)) else: - newdata = None + resp = None except EOFError: self.log.debug("done polling") return {'FINISHED'} - if newdata is not None: - self.handle_response(newdata) #TODO: make this a customizable callback + if resp is not None: + if resp.exception is not None: + raise resp.exception + elif resp.data is not None: + self.handle_response(resp.data) #TODO: make this a customizable callback # this should allow chaining of multiple subprocess in a single operator return {'PASS_THROUGH'} @@ -75,3 +82,28 @@ def subprocess_operator(cls: Operator, polling_interval=.01) -> Operator: setattr(cls, 'poll_subprocess', poll_subprocess) return cls + +def subprocess_function(func, *args, pipe, **kwargs): + """ + Wrapper which feeds the return val of `func` into the latter end of a pipe + """ + def wrapper(*args, **kwargs): + # we have to explicitly close the end of the pipe we are NOT using, + # otherwise no exception will be generated when the other process closes its end. + pipe[0].close() + + try: + return_val = func(*args, **kwargs) + pipe[1].send(SubprocessMessage(data=return_val)) + except Exception as err: + log.debug("Caught exception from subprocess: %s", err) + pipe[1].send(SubprocessMessage(exception=err)) + finally: + pipe[1].close() + + return wrapper + +class SubprocessMessage: + def __init__(self, exception=None, data=None): + self.exception = exception + self.data = data