Merge branch 'windows-subprocess-fix'

This commit is contained in:
Ellwood Zwovic
2017-08-19 19:00:52 -07:00

View File

@@ -12,11 +12,15 @@ bl_info = {
'category': 'System', 'category': 'System',
'support': 'TESTING', 'support': 'TESTING',
} }
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
if 'bpy' in locals(): try:
import bpy
except ImportError:
from . import subproc
else:
if 'bpkg' in locals():
from importlib import reload from importlib import reload
def recursive_reload(mod): def recursive_reload(mod):
@@ -35,7 +39,6 @@ if 'bpy' in locals():
bpkg = recursive_reload(bpkg) bpkg = recursive_reload(bpkg)
Package = bpkg.types.Package Package = bpkg.types.Package
else:
from . import subproc from . import subproc
from . import messages from . import messages
from . import bpkg from . import bpkg
@@ -44,18 +47,19 @@ else:
Package, Package,
ConsolidatedPackage, ConsolidatedPackage,
) )
from pathlib import Path
from collections import OrderedDict
import multiprocessing
mp_context = multiprocessing.get_context('spawn')
mp_context.set_executable(bpy.app.binary_path_python)
import bpy # global list of all known packages, indexed by name
from pathlib import Path _packages = OrderedDict()
from collections import OrderedDict
# global list of all known packages, indexed by name # used for lazy loading
_packages = OrderedDict() _main_has_run = False
# used for lazy loading class SubprocMixin:
_main_has_run = False
class SubprocMixin:
"""Mix-in class for things that need to be run in a subprocess.""" """Mix-in class for things that need to be run in a subprocess."""
log = logging.getLogger(__name__ + '.SubprocMixin') log = logging.getLogger(__name__ + '.SubprocMixin')
@@ -74,8 +78,6 @@ class SubprocMixin:
self._state = 'QUIT' self._state = 'QUIT'
def invoke(self, context, event): def invoke(self, context, event):
import multiprocessing
self.pipe_blender, self.pipe_subproc = multiprocessing.Pipe() self.pipe_blender, self.pipe_subproc = multiprocessing.Pipe()
# The subprocess should just be terminated when Blender quits. Without this, # The subprocess should just be terminated when Blender quits. Without this,
@@ -135,7 +137,6 @@ class SubprocMixin:
self.pipe_blender.send(messages.Abort()) self.pipe_blender.send(messages.Abort())
def _finish(self, context): def _finish(self, context):
import multiprocessing
try: try:
self.cancel(context) self.cancel(context)
except AttributeError: except AttributeError:
@@ -185,7 +186,7 @@ class SubprocMixin:
raise NotImplementedError() raise NotImplementedError()
class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator): class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator):
bl_idname = 'package.install' bl_idname = 'package.install'
bl_label = 'Install package' bl_label = 'Install package'
bl_description = 'Downloads and installs a Blender add-on package' bl_description = 'Downloads and installs a Blender add-on package'
@@ -213,8 +214,6 @@ class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator):
:rtype: multiprocessing.Process :rtype: multiprocessing.Process
""" """
import multiprocessing
self.msg_handlers = { self.msg_handlers = {
messages.Progress: self._subproc_progress, messages.Progress: self._subproc_progress,
messages.DownloadError: self._subproc_download_error, messages.DownloadError: self._subproc_download_error,
@@ -234,7 +233,7 @@ class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator):
self.log.debug("Using %s as install path", install_path) self.log.debug("Using %s as install path", install_path)
import addon_utils import addon_utils
proc = multiprocessing.Process(target=subproc.download_and_install_package, proc = mp_context.Process(target=subproc.download_and_install_package,
args=(self.pipe_subproc, package, install_path)) args=(self.pipe_subproc, package, install_path))
return proc return proc
@@ -267,7 +266,7 @@ class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator):
self.log.error('Process died without telling us! Exit code was 0 though') 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.') self.report({'WARNING'}, 'Error downloading package, but process finished OK. This is weird.')
class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator): class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator):
bl_idname = 'package.uninstall' bl_idname = 'package.uninstall'
bl_label = 'Install package' bl_label = 'Install package'
bl_description = "Remove installed package files from filesystem" bl_description = "Remove installed package files from filesystem"
@@ -289,8 +288,6 @@ class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator):
:rtype: multiprocessing.Process :rtype: multiprocessing.Process
""" """
import multiprocessing
self.msg_handlers = { self.msg_handlers = {
messages.UninstallError: self._subproc_uninstall_error, messages.UninstallError: self._subproc_uninstall_error,
messages.Success: self._subproc_success, messages.Success: self._subproc_success,
@@ -302,7 +299,7 @@ class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator):
global _packages global _packages
package = _packages[self.package_name].get_latest_version() package = _packages[self.package_name].get_latest_version()
proc = multiprocessing.Process(target=subproc.uninstall_package, proc = mp_context.Process(target=subproc.uninstall_package,
args=(self.pipe_subproc, package, install_path)) args=(self.pipe_subproc, package, install_path))
return proc return proc
@@ -325,7 +322,7 @@ class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator):
self.report({'WARNING'}, 'Error downloading package, but process finished OK. This is weird.') self.report({'WARNING'}, 'Error downloading package, but process finished OK. This is weird.')
def get_installed_packages(refresh=False) -> list: def get_installed_packages(refresh=False) -> list:
"""Get list of packages installed on disk""" """Get list of packages installed on disk"""
import addon_utils import addon_utils
installed_pkgs = [] installed_pkgs = []
@@ -335,10 +332,10 @@ def get_installed_packages(refresh=False) -> list:
installed_pkgs.append(pkg) installed_pkgs.append(pkg)
return installed_pkgs return installed_pkgs
def get_repo_storage_path() -> Path: def get_repo_storage_path() -> Path:
return Path(bpy.utils.user_resource('CONFIG', 'repositories')) return Path(bpy.utils.user_resource('CONFIG', 'repositories'))
def get_repositories() -> list: def get_repositories() -> list:
""" """
Get list of downloaded repositories and update wm.package_repositories Get list of downloaded repositories and update wm.package_repositories
""" """
@@ -349,23 +346,23 @@ def get_repositories() -> list:
return repos return repos
# class PACKAGE_OT_refresh_packages(bpy.types.Operator): # class PACKAGE_OT_refresh_packages(bpy.types.Operator):
# bl_idname = "package.refresh_packages" # bl_idname = "package.refresh_packages"
# bl_label = "Refresh Packages" # bl_label = "Refresh Packages"
# bl_description = "Scan for packages on disk" # bl_description = "Scan for packages on disk"
# #
# log = logging.getLogger(__name__ + ".PACKAGE_OT_refresh_packages") # log = logging.getLogger(__name__ + ".PACKAGE_OT_refresh_packages")
# #
# def execute(self, context): # def execute(self, context):
# global _packages # global _packages
# installed_packages = get_packages_from_disk(refresh=True) # installed_packages = get_packages_from_disk(refresh=True)
# available_packages = get_packages_from_repo() # available_packages = get_packages_from_repo()
# _packages = build_composite_packagelist(installed_packages, available_packages) # _packages = build_composite_packagelist(installed_packages, available_packages)
# context.area.tag_redraw() # context.area.tag_redraw()
# #
# return {'FINISHED'} # return {'FINISHED'}
class PACKAGE_OT_refresh(SubprocMixin, bpy.types.Operator): class PACKAGE_OT_refresh(SubprocMixin, bpy.types.Operator):
bl_idname = "package.refresh" bl_idname = "package.refresh"
bl_label = "Refresh" bl_label = "Refresh"
bl_description = 'Check repositories for new and updated packages' bl_description = 'Check repositories for new and updated packages'
@@ -400,8 +397,6 @@ class PACKAGE_OT_refresh(SubprocMixin, bpy.types.Operator):
:rtype: multiprocessing.Process :rtype: multiprocessing.Process
""" """
import multiprocessing
#TODO: make sure all possible messages are handled #TODO: make sure all possible messages are handled
self.msg_handlers = { self.msg_handlers = {
messages.Progress: self._subproc_progress, messages.Progress: self._subproc_progress,
@@ -419,7 +414,7 @@ class PACKAGE_OT_refresh(SubprocMixin, bpy.types.Operator):
repository_urls = [repo.url for repo in self.repositories] repository_urls = [repo.url for repo in self.repositories]
self.log.debug("Repository urls %s", repository_urls) self.log.debug("Repository urls %s", repository_urls)
proc = multiprocessing.Process(target=subproc.refresh_repositories, proc = mp_context.Process(target=subproc.refresh_repositories,
args=(self.pipe_subproc, storage_path, repository_urls)) args=(self.pipe_subproc, storage_path, repository_urls))
return proc return proc
@@ -465,7 +460,7 @@ class PACKAGE_OT_refresh(SubprocMixin, bpy.types.Operator):
self.log.error('Refresh process died without telling us! Exit code was 0 though') self.log.error('Refresh process died without telling us! Exit code was 0 though')
self.report({'WARNING'}, 'Error refreshing package lists, but process finished OK. This is weird.') self.report({'WARNING'}, 'Error refreshing package lists, but process finished OK. This is weird.')
class RepositoryProperty(bpy.types.PropertyGroup): class RepositoryProperty(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Name") name = bpy.props.StringProperty(name="Name")
url = bpy.props.StringProperty(name="URL") url = bpy.props.StringProperty(name="URL")
status = bpy.props.EnumProperty(name="Status", items=[ status = bpy.props.EnumProperty(name="Status", items=[
@@ -475,7 +470,7 @@ class RepositoryProperty(bpy.types.PropertyGroup):
]) ])
enabled = bpy.props.BoolProperty(name="Enabled") enabled = bpy.props.BoolProperty(name="Enabled")
class PACKAGE_UL_repositories(bpy.types.UIList): class PACKAGE_UL_repositories(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
layout.alignment='LEFT' layout.alignment='LEFT'
layout.prop(item, "enabled", text="") layout.prop(item, "enabled", text="")
@@ -484,7 +479,7 @@ class PACKAGE_UL_repositories(bpy.types.UIList):
else: else:
layout.label(item.name) layout.label(item.name)
class PACKAGE_OT_add_repository(bpy.types.Operator): class PACKAGE_OT_add_repository(bpy.types.Operator):
bl_idname = "package.add_repository" bl_idname = "package.add_repository"
bl_label = "Add Repository" bl_label = "Add Repository"
@@ -509,7 +504,7 @@ class PACKAGE_OT_add_repository(bpy.types.Operator):
context.area.tag_redraw() context.area.tag_redraw()
return {'FINISHED'} return {'FINISHED'}
class PACKAGE_OT_remove_repository(bpy.types.Operator): class PACKAGE_OT_remove_repository(bpy.types.Operator):
bl_idname = "package.remove_repository" bl_idname = "package.remove_repository"
bl_label = "Remove Repository" bl_label = "Remove Repository"
@@ -529,7 +524,7 @@ class PACKAGE_OT_remove_repository(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class USERPREF_PT_packages(bpy.types.Panel): class USERPREF_PT_packages(bpy.types.Panel):
bl_label = "Package Management" bl_label = "Package Management"
bl_space_type = 'USER_PREFERENCES' bl_space_type = 'USER_PREFERENCES'
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
@@ -865,7 +860,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
draw_package(_packages[pkgname], row) draw_package(_packages[pkgname], row)
class WM_OT_package_toggle_expand(bpy.types.Operator):# {{{ class WM_OT_package_toggle_expand(bpy.types.Operator):# {{{
bl_idname = "wm.package_toggle_expand" bl_idname = "wm.package_toggle_expand"
bl_label = "" bl_label = ""
bl_description = "Toggle display of extended information for given package (hold shift to collapse all other packages)" bl_description = "Toggle display of extended information for given package (hold shift to collapse all other packages)"
@@ -888,7 +883,7 @@ class WM_OT_package_toggle_expand(bpy.types.Operator):# {{{
return {'FINISHED'}# }}} return {'FINISHED'}# }}}
class PACKAGE_OT_toggle_enabled(bpy.types.Operator):# {{{ class PACKAGE_OT_toggle_enabled(bpy.types.Operator):# {{{
bl_idname = "package.toggle_enabled" bl_idname = "package.toggle_enabled"
bl_label = "" bl_label = ""
bl_description = "Enable given package if it's disabled, and vice versa if it's enabled" bl_description = "Enable given package if it's disabled, and vice versa if it's enabled"
@@ -929,7 +924,7 @@ class PACKAGE_OT_toggle_enabled(bpy.types.Operator):# {{{
return {'FINISHED'}# }}} return {'FINISHED'}# }}}
class PACKAGE_OT_disable(bpy.types.Operator):# {{{ class PACKAGE_OT_disable(bpy.types.Operator):# {{{
bl_idname = "package.disable" bl_idname = "package.disable"
bl_label = "" bl_label = ""
bl_description = "Disable given package" bl_description = "Disable given package"
@@ -954,16 +949,16 @@ class PACKAGE_OT_disable(bpy.types.Operator):# {{{
_packages[self.package_name].enabled = False _packages[self.package_name].enabled = False
return ret# }}} return ret# }}}
# class PackageManagerPreferences(bpy.types.AddonPreferences): # class PackageManagerPreferences(bpy.types.AddonPreferences):
# bl_idname = __package__ # bl_idname = __package__
# #
# repositories = bpy.props.CollectionProperty( # repositories = bpy.props.CollectionProperty(
# type=RepositoryProperty, # type=RepositoryProperty,
# name="Repositories", # name="Repositories",
# ) # )
# active_repository = bpy.props.IntProperty() # active_repository = bpy.props.IntProperty()
def build_packagelist() -> OrderedDict:# {{{ def build_packagelist() -> OrderedDict:# {{{
"""Make an OrderedDict of ConsolidatedPackages from known repositories + installed packages, keyed by package name""" """Make an OrderedDict of ConsolidatedPackages from known repositories + installed packages, keyed by package name"""
log = logging.getLogger(__name__ + ".build_composite_packagelist") log = logging.getLogger(__name__ + ".build_composite_packagelist")
@@ -990,7 +985,7 @@ def build_packagelist() -> OrderedDict:# {{{
# log.debug(masterlist[None].__dict__) # log.debug(masterlist[None].__dict__)
return OrderedDict(sorted(masterlist.items()))# }}} return OrderedDict(sorted(masterlist.items()))# }}}
def main(): def main():
"""Entry point; performs initial loading of repositories and installed packages""" """Entry point; performs initial loading of repositories and installed packages"""
global _packages global _packages
global _main_has_run global _main_has_run
@@ -1009,7 +1004,7 @@ def main():
_main_has_run = True _main_has_run = True
def register(): def register():
bpy.utils.register_class(PACKAGE_OT_install) bpy.utils.register_class(PACKAGE_OT_install)
bpy.utils.register_class(PACKAGE_OT_uninstall) bpy.utils.register_class(PACKAGE_OT_uninstall)
bpy.utils.register_class(PACKAGE_OT_toggle_enabled) bpy.utils.register_class(PACKAGE_OT_toggle_enabled)
@@ -1046,7 +1041,7 @@ def register():
# bpy.utils.register_class(PackageManagerPreferences) # bpy.utils.register_class(PackageManagerPreferences)
def unregister(): def unregister():
bpy.utils.unregister_class(PACKAGE_OT_install) bpy.utils.unregister_class(PACKAGE_OT_install)
bpy.utils.unregister_class(PACKAGE_OT_uninstall) bpy.utils.unregister_class(PACKAGE_OT_uninstall)
bpy.utils.unregister_class(PACKAGE_OT_toggle_enabled) bpy.utils.unregister_class(PACKAGE_OT_toggle_enabled)