Add uninstall function and operator

For now the install/uninstall operators only operate on the newest
available version of a given package.
This commit is contained in:
Ellwood Zwovic
2017-07-20 19:22:45 -07:00
parent e32c920368
commit 9740d3fce7
2 changed files with 200 additions and 35 deletions

View File

@@ -173,7 +173,7 @@ class BPKG_OT_install(SubprocMixin, bpy.types.Operator):
def create_subprocess(self):
"""Starts the download process.
tuple to
Also registers the message handlers.
:rtype: multiprocessing.Process
@@ -198,7 +198,6 @@ class BPKG_OT_install(SubprocMixin, bpy.types.Operator):
self.log.debug("Using %s as install path", install_path)
import addon_utils
proc = multiprocessing.Process(target=subproc.download_and_install,
args=(self.pipe_subproc, self.package_url, install_path, addon_utils.paths()))
return proc
@@ -235,6 +234,65 @@ class BPKG_OT_install(SubprocMixin, bpy.types.Operator):
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 BPKG_OT_uninstall(SubprocMixin, bpy.types.Operator):
bl_idname = 'bpkg.uninstall'
bl_label = 'Install package'
bl_description = 'Downloads and installs a Blender add-on package'
bl_options = {'REGISTER'}
package_name = bpy.props.StringProperty(name='package_name', description='The name of the package to uninstall')
log = logging.getLogger(__name__ + '.BPKG_OT_uninstall')
def invoke(self, context, event):
if not self.package_name:
self.report({'ERROR'}, 'Package name not given')
return {'CANCELLED'}
return super().invoke(context, event)
def create_subprocess(self):
"""Starts the uninstall process and registers the message handlers.
:rtype: multiprocessing.Process
"""
import multiprocessing
self.msg_handlers = {
subproc.UninstallError: self._subproc_uninstall_error,
subproc.Success: self._subproc_success,
}
import pathlib
install_path = pathlib.Path(bpy.utils.user_resource('SCRIPTS', 'addons', create=True))
# TODO: only drawing-related package data should be stored on the panel. Maybe move this to a global..?
package = USERPREF_PT_packages.all_packages[self.package_name].get_latest_version()
proc = multiprocessing.Process(target=subproc.uninstall,
args=(self.pipe_subproc, package, install_path))
return proc
def _subproc_uninstall_error(self, error: subproc.InstallError):
self.report({'ERROR'}, 'Unable to install package: %s' % error.message)
self.quit()
def _subproc_success(self, success: subproc.Success):
self.report({'INFO'}, 'Package uninstalled successfully')
getattr(bpy.ops, __package__).refresh_packages()
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.')
def get_packages_from_disk(refresh=False) -> list:
"""Get list of packages installed on disk"""
import addon_utils
@@ -244,7 +302,10 @@ def get_packages_from_repo() -> list:
"""Get list of packages from cached repository lists (does not refresh them from server)"""
import pathlib
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'packages', create=True))
return subproc._load_repo(storage_path).packages
repo = subproc._load_repo(storage_path)
for pkg in repo.packages:
pkg.repository = repo.name
return repo.packages
class BPKG_OT_refresh_packages(bpy.types.Operator):
bl_idname = "bpkg.refresh_packages"
@@ -254,8 +315,9 @@ class BPKG_OT_refresh_packages(bpy.types.Operator):
log = logging.getLogger(__name__ + ".BPKG_OT_refresh_packages")
def execute(self, context):
installed_packages = get_packages_from_disk()
USERPREF_PT_packages.all_packages = combine_packagelists(installed_packages, USERPREF_PT_packages.available_packages)
installed_packages = get_packages_from_disk(refresh=True)
available_packages = get_packages_from_repo()
USERPREF_PT_packages.all_packages = build_composite_packagelist(installed_packages, available_packages)
return {'FINISHED'}
@@ -321,7 +383,15 @@ class BPKG_OT_refresh_repositories(SubprocMixin, bpy.types.Operator):
self.quit()
def _subproc_repository_result(self, result: subproc.RepositoryResult):
USERPREF_PT_packages.available_packages = result.repository['packages']
available_packages = result.repository.packages
installed_packages = get_packages_from_disk(refresh=False)
# TODO: deduplicate creation of view-packages..
for pkg in available_packages:
pkg.repository = result.repository.name
USERPREF_PT_packages.all_packages = build_composite_packagelist(installed_packages, available_packages)
USERPREF_PT_packages.available_packages = available_packages
self.report({'INFO'}, 'Package list retrieved successfully')
def _subproc_aborted(self, aborted: subproc.Aborted):
@@ -346,7 +416,7 @@ class BPKG_OT_refresh(bpy.types.Operator):
def execute(self, context):
getattr(bpy.ops, __package__).refresh_repositories()
getattr(bpy.ops, __package__).refresh_packages()
# getattr(bpy.ops, __package__).refresh_packages()
return {'FINISHED'}
@@ -545,26 +615,48 @@ class USERPREF_PT_packages(bpy.types.Panel):
lr2.label(text=blinfo.get('description', ""))
lr2.enabled = False #Give name more visual weight
if pkg.versions[0].url:
right.operator(BPKG_OT_install.bl_idname,
text="Install").package_url=pkg.versions[0].url
latest_pkg = pkg.get_latest_version()
if latest_pkg.installed:
if latest_pkg.url:
right.operator(BPKG_OT_uninstall.bl_idname,
text="Uninstall").package_name=latest_pkg.name
else:
right.label("Installed")
else:
if latest_pkg.url:
right.operator(BPKG_OT_install.bl_idname,
text="Install").package_url=pkg.versions[0].url
else:
right.label("Not installed, but no URL?")
def expanded():
row1 = leftcol.row()
row1.label(blinfo.get('name'), "")
def string_version(version_number) -> str:
"""Take version number as an iterable and format it as a string"""
vstr = str(version_number[0])
for component in version_number[1:]:
vstr += "." + str(component)
return vstr
if blinfo.get('description'):
row2 = leftcol.row()
row2.label(blinfo['description'])
# row2.scale_y = 1.2
if blinfo.get('version'):
vstr = str(blinfo['version'][0])
for component in blinfo['version'][1:]:
vstr += "." + str(component)
spl = leftcol.row().split(.15)
spl.label("Version:")
spl.label(vstr)
spl.label(string_version(blinfo['version']))
def draw_metadatum(key: str, value: str, layout: bpy.types.UILayout):
"""Draw the given key value pair in a new row in given layout container"""
row = layout.row()
row.scale_y = .8
spl = row.split(.15)
spl.label("{}:".format(key))
spl.label(value)
for prop in (
# "description",
@@ -585,6 +677,32 @@ class USERPREF_PT_packages(bpy.types.Panel):
spl.label("{}:".format(prop.title()))
spl.label(str(blinfo[prop]))
def draw_version(layout: bpy.types.UILayout, pkg: Package):
"""Draw version of package"""
spl = layout.split(.9)
left = spl.column()
right = spl.column()
right.alignment = 'RIGHT'
left.label(text=string_version(pkg.version))
if pkg.repository is not None:
draw_metadatum("Repository", pkg.repository, left)
if pkg.installed:
right.label(text="Installed")
draw_metadatum("Installed to", str(pkg.installed_location), left)
if len(pkg.versions) > 1:
row = pkgbox.row()
row.label(text="There are multiple providers of this package:")
for version in pkg.versions:
# row = pkgbox.row()
subvbox = pkgbox.box()
draw_version(subvbox, version)
if pkg.expanded:
expanded()
else:
@@ -609,7 +727,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
center_message(pkgzone, "No repositories found")
return
all_packages = combine_packagelists(installed_packages, available_packages)
all_packages = build_composite_packagelist(installed_packages, available_packages)
if len(all_packages) == 0:
center_message(pkgzone, "No packages found")
return
@@ -640,8 +758,13 @@ class ViewPackage:
if pkg is not None:
self.add_version(pkg)
def get_latest_version(self) -> Package:
"""Get package with highest version number"""
return self.versions[0] # this is always sorted with the highest on top
def add_version(self, pkg: Package):
self.versions.append(pkg)
self.versions.sort(key=lambda v: v.version, reverse=True)
def __iter__(self):
return (pkg for pkg in self.versions)
@@ -698,19 +821,17 @@ def validate_packagelist(pkglist: list) -> list:
"""Ensures all packages have required fields; strips out bad packages and returns them in a list"""
pass
def combine_packagelists(installed: list, available: list) -> OrderedDict:
def build_composite_packagelist(installed: list, available: list) -> OrderedDict:
"""Merge list of installed and available packages into one dict, keyed by package name"""
log = logging.getLogger(__name__ + ".combine_packagelists")
log = logging.getLogger(__name__ + ".build_composite_packagelist")
masterlist = {}
def packages_are_equivilent(pkg1: Package, pkg2: Package) -> bool:
"""Check that packages are the same version and provide the same files"""
blinfo1 = pkg1.bl_info
blinfo2 = pkg2.bl_info
return blinfo1['version'] == blinfo2['version']\
and blinfo1['files'] == blinfo2['files']
return pkg1.version == pkg2.version\
and pkg1.files == pkg2.files
for pkg in available:
pkgname = pkg.bl_info['name']
@@ -720,22 +841,24 @@ def combine_packagelists(installed: list, available: list) -> OrderedDict:
masterlist[pkgname] = ViewPackage(pkg)
for pkg in installed:
pkgname = pkg.bl_info['name']
if pkgname in masterlist:
for masterpkg in masterlist[pkgname]:
pkg.installed = True
if pkg.name in masterlist:
for masterpkg in masterlist[pkg.name]:
log.debug("{} and {} equivilent? {}".format((pkg.name, pkg.version), (masterpkg.name, masterpkg.version), packages_are_equivilent(pkg, masterpkg)))
if packages_are_equivilent(pkg, masterpkg):
masterpkg.installed = True
masterpkg.installed_location = pkg.installed_location
break
else:
pkg.installed = True
masterlist[pkgname].add_version(pkg)
masterlist[pkg.name].add_version(pkg)
else:
masterlist[pkgname] = ViewPackage(pkg)
masterlist[pkg.name] = ViewPackage(pkg)
return OrderedDict(sorted(masterlist.items()))
def register():
bpy.utils.register_class(BPKG_OT_install)
bpy.utils.register_class(BPKG_OT_uninstall)
bpy.utils.register_class(BPKG_OT_refresh_repositories)
bpy.utils.register_class(BPKG_OT_refresh_packages)
bpy.utils.register_class(BPKG_OT_refresh)
@@ -761,6 +884,7 @@ def register():
def unregister():
bpy.utils.unregister_class(BPKG_OT_install)
bpy.utils.unregister_class(BPKG_OT_uninstall)
bpy.utils.unregister_class(BPKG_OT_refresh_repositories)
bpy.utils.unregister_class(BPKG_OT_refresh_packages)
bpy.utils.unregister_class(BPKG_OT_refresh)