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:
178
bpkg/__init__.py
178
bpkg/__init__.py
@@ -173,7 +173,7 @@ class BPKG_OT_install(SubprocMixin, bpy.types.Operator):
|
|||||||
|
|
||||||
def create_subprocess(self):
|
def create_subprocess(self):
|
||||||
"""Starts the download process.
|
"""Starts the download process.
|
||||||
|
tuple to
|
||||||
Also registers the message handlers.
|
Also registers the message handlers.
|
||||||
|
|
||||||
:rtype: multiprocessing.Process
|
: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)
|
self.log.debug("Using %s as install path", install_path)
|
||||||
|
|
||||||
import addon_utils
|
import addon_utils
|
||||||
|
|
||||||
proc = multiprocessing.Process(target=subproc.download_and_install,
|
proc = multiprocessing.Process(target=subproc.download_and_install,
|
||||||
args=(self.pipe_subproc, self.package_url, install_path, addon_utils.paths()))
|
args=(self.pipe_subproc, self.package_url, install_path, addon_utils.paths()))
|
||||||
return proc
|
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.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 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:
|
def get_packages_from_disk(refresh=False) -> list:
|
||||||
"""Get list of packages installed on disk"""
|
"""Get list of packages installed on disk"""
|
||||||
import addon_utils
|
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)"""
|
"""Get list of packages from cached repository lists (does not refresh them from server)"""
|
||||||
import pathlib
|
import pathlib
|
||||||
storage_path = pathlib.Path(bpy.utils.user_resource('CONFIG', 'packages', create=True))
|
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):
|
class BPKG_OT_refresh_packages(bpy.types.Operator):
|
||||||
bl_idname = "bpkg.refresh_packages"
|
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")
|
log = logging.getLogger(__name__ + ".BPKG_OT_refresh_packages")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
installed_packages = get_packages_from_disk()
|
installed_packages = get_packages_from_disk(refresh=True)
|
||||||
USERPREF_PT_packages.all_packages = combine_packagelists(installed_packages, USERPREF_PT_packages.available_packages)
|
available_packages = get_packages_from_repo()
|
||||||
|
USERPREF_PT_packages.all_packages = build_composite_packagelist(installed_packages, available_packages)
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@@ -321,7 +383,15 @@ class BPKG_OT_refresh_repositories(SubprocMixin, bpy.types.Operator):
|
|||||||
self.quit()
|
self.quit()
|
||||||
|
|
||||||
def _subproc_repository_result(self, result: subproc.RepositoryResult):
|
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')
|
self.report({'INFO'}, 'Package list retrieved successfully')
|
||||||
|
|
||||||
def _subproc_aborted(self, aborted: subproc.Aborted):
|
def _subproc_aborted(self, aborted: subproc.Aborted):
|
||||||
@@ -346,7 +416,7 @@ class BPKG_OT_refresh(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
getattr(bpy.ops, __package__).refresh_repositories()
|
getattr(bpy.ops, __package__).refresh_repositories()
|
||||||
getattr(bpy.ops, __package__).refresh_packages()
|
# getattr(bpy.ops, __package__).refresh_packages()
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@@ -545,26 +615,48 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
|||||||
lr2.label(text=blinfo.get('description', ""))
|
lr2.label(text=blinfo.get('description', ""))
|
||||||
lr2.enabled = False #Give name more visual weight
|
lr2.enabled = False #Give name more visual weight
|
||||||
|
|
||||||
if pkg.versions[0].url:
|
latest_pkg = pkg.get_latest_version()
|
||||||
right.operator(BPKG_OT_install.bl_idname,
|
if latest_pkg.installed:
|
||||||
text="Install").package_url=pkg.versions[0].url
|
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():
|
def expanded():
|
||||||
row1 = leftcol.row()
|
row1 = leftcol.row()
|
||||||
row1.label(blinfo.get('name'), "")
|
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'):
|
if blinfo.get('description'):
|
||||||
row2 = leftcol.row()
|
row2 = leftcol.row()
|
||||||
row2.label(blinfo['description'])
|
row2.label(blinfo['description'])
|
||||||
# row2.scale_y = 1.2
|
# row2.scale_y = 1.2
|
||||||
|
|
||||||
if blinfo.get('version'):
|
if blinfo.get('version'):
|
||||||
vstr = str(blinfo['version'][0])
|
|
||||||
for component in blinfo['version'][1:]:
|
|
||||||
vstr += "." + str(component)
|
|
||||||
spl = leftcol.row().split(.15)
|
spl = leftcol.row().split(.15)
|
||||||
spl.label("Version:")
|
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 (
|
for prop in (
|
||||||
# "description",
|
# "description",
|
||||||
@@ -585,6 +677,32 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
|||||||
spl.label("{}:".format(prop.title()))
|
spl.label("{}:".format(prop.title()))
|
||||||
spl.label(str(blinfo[prop]))
|
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:
|
if pkg.expanded:
|
||||||
expanded()
|
expanded()
|
||||||
else:
|
else:
|
||||||
@@ -609,7 +727,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
|||||||
center_message(pkgzone, "No repositories found")
|
center_message(pkgzone, "No repositories found")
|
||||||
return
|
return
|
||||||
|
|
||||||
all_packages = combine_packagelists(installed_packages, available_packages)
|
all_packages = build_composite_packagelist(installed_packages, available_packages)
|
||||||
if len(all_packages) == 0:
|
if len(all_packages) == 0:
|
||||||
center_message(pkgzone, "No packages found")
|
center_message(pkgzone, "No packages found")
|
||||||
return
|
return
|
||||||
@@ -640,8 +758,13 @@ class ViewPackage:
|
|||||||
if pkg is not None:
|
if pkg is not None:
|
||||||
self.add_version(pkg)
|
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):
|
def add_version(self, pkg: Package):
|
||||||
self.versions.append(pkg)
|
self.versions.append(pkg)
|
||||||
|
self.versions.sort(key=lambda v: v.version, reverse=True)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return (pkg for pkg in self.versions)
|
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"""
|
"""Ensures all packages have required fields; strips out bad packages and returns them in a list"""
|
||||||
pass
|
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"""
|
"""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 = {}
|
masterlist = {}
|
||||||
|
|
||||||
def packages_are_equivilent(pkg1: Package, pkg2: Package) -> bool:
|
def packages_are_equivilent(pkg1: Package, pkg2: Package) -> bool:
|
||||||
"""Check that packages are the same version and provide the same files"""
|
"""Check that packages are the same version and provide the same files"""
|
||||||
blinfo1 = pkg1.bl_info
|
return pkg1.version == pkg2.version\
|
||||||
blinfo2 = pkg2.bl_info
|
and pkg1.files == pkg2.files
|
||||||
return blinfo1['version'] == blinfo2['version']\
|
|
||||||
and blinfo1['files'] == blinfo2['files']
|
|
||||||
|
|
||||||
for pkg in available:
|
for pkg in available:
|
||||||
pkgname = pkg.bl_info['name']
|
pkgname = pkg.bl_info['name']
|
||||||
@@ -720,22 +841,24 @@ def combine_packagelists(installed: list, available: list) -> OrderedDict:
|
|||||||
masterlist[pkgname] = ViewPackage(pkg)
|
masterlist[pkgname] = ViewPackage(pkg)
|
||||||
|
|
||||||
for pkg in installed:
|
for pkg in installed:
|
||||||
pkgname = pkg.bl_info['name']
|
pkg.installed = True
|
||||||
if pkgname in masterlist:
|
if pkg.name in masterlist:
|
||||||
for masterpkg in masterlist[pkgname]:
|
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):
|
if packages_are_equivilent(pkg, masterpkg):
|
||||||
masterpkg.installed = True
|
masterpkg.installed = True
|
||||||
|
masterpkg.installed_location = pkg.installed_location
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
pkg.installed = True
|
masterlist[pkg.name].add_version(pkg)
|
||||||
masterlist[pkgname].add_version(pkg)
|
|
||||||
else:
|
else:
|
||||||
masterlist[pkgname] = ViewPackage(pkg)
|
masterlist[pkg.name] = ViewPackage(pkg)
|
||||||
|
|
||||||
return OrderedDict(sorted(masterlist.items()))
|
return OrderedDict(sorted(masterlist.items()))
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(BPKG_OT_install)
|
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_repositories)
|
||||||
bpy.utils.register_class(BPKG_OT_refresh_packages)
|
bpy.utils.register_class(BPKG_OT_refresh_packages)
|
||||||
bpy.utils.register_class(BPKG_OT_refresh)
|
bpy.utils.register_class(BPKG_OT_refresh)
|
||||||
@@ -761,6 +884,7 @@ def register():
|
|||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.utils.unregister_class(BPKG_OT_install)
|
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_repositories)
|
||||||
bpy.utils.unregister_class(BPKG_OT_refresh_packages)
|
bpy.utils.unregister_class(BPKG_OT_refresh_packages)
|
||||||
bpy.utils.unregister_class(BPKG_OT_refresh)
|
bpy.utils.unregister_class(BPKG_OT_refresh)
|
||||||
|
@@ -48,6 +48,9 @@ class SubprocWarning(SubprocMessage):
|
|||||||
class InstallError(SubprocError):
|
class InstallError(SubprocError):
|
||||||
"""Sent when there was an error installing something."""
|
"""Sent when there was an error installing something."""
|
||||||
|
|
||||||
|
class UninstallError(SubprocError):
|
||||||
|
"""Sent when there was an error uninstalling something."""
|
||||||
|
|
||||||
class FileConflictError(InstallError):
|
class FileConflictError(InstallError):
|
||||||
"""Sent when installation would overwrite existing files."""
|
"""Sent when installation would overwrite existing files."""
|
||||||
|
|
||||||
@@ -149,6 +152,9 @@ class Package:
|
|||||||
self.files = []
|
self.files = []
|
||||||
self.set_from_dict(package_dict)
|
self.set_from_dict(package_dict)
|
||||||
|
|
||||||
|
self.installed = False
|
||||||
|
self.repository = None
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Return a dict representation of the package
|
Return a dict representation of the package
|
||||||
@@ -171,14 +177,21 @@ class Package:
|
|||||||
setattr(self, attr, package_dict[attr])
|
setattr(self, attr, package_dict[attr])
|
||||||
|
|
||||||
#bl_info convenience getters
|
#bl_info convenience getters
|
||||||
def get_name() -> str:
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
"""Get name from bl_info"""
|
"""Get name from bl_info"""
|
||||||
return self.bl_info['name']
|
return self.bl_info['name']
|
||||||
|
|
||||||
def get_description() -> str:
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
"""Get description from bl_info"""
|
"""Get description from bl_info"""
|
||||||
return self.bl_info['description']
|
return self.bl_info['description']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> tuple:
|
||||||
|
"""Get version from bl_info"""
|
||||||
|
return tuple(self.bl_info['version'])
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def from_dict(cls, package_dict: dict):
|
# def from_dict(cls, package_dict: dict):
|
||||||
# """
|
# """
|
||||||
@@ -205,7 +218,8 @@ class Package:
|
|||||||
filepath = filepath.parent
|
filepath = filepath.parent
|
||||||
|
|
||||||
pkg = cls()
|
pkg = cls()
|
||||||
pkg.files = [filepath]
|
pkg.files = [filepath.name]
|
||||||
|
pkg.installed_location = str(filepath)
|
||||||
try:
|
try:
|
||||||
pkg.bl_info = module.bl_info
|
pkg.bl_info = module.bl_info
|
||||||
except AttributeError as err:
|
except AttributeError as err:
|
||||||
@@ -381,7 +395,11 @@ def _download(pipe_to_blender, package_url: str, download_dir: pathlib.Path) ->
|
|||||||
pipe_to_blender.send(Progress(0.0))
|
pipe_to_blender.send(Progress(0.0))
|
||||||
|
|
||||||
log.info('Downloading %s', package_url)
|
log.info('Downloading %s', package_url)
|
||||||
resp = requests.get(package_url, stream=True, verify=True)
|
try:
|
||||||
|
resp = requests.get(package_url, stream=True, verify=True)
|
||||||
|
except requests.exceptions.RequestException as err:
|
||||||
|
pipe_to_blender.send(DownloadError(1, err))
|
||||||
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
@@ -522,7 +540,7 @@ def _install(pipe_to_blender, pkgpath: pathlib.Path, dest: pathlib.Path, searchp
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def download_and_install(pipe_to_blender, package: dict, install_path: pathlib.Path, repo_path: pathlib.Path, search_paths: list):
|
def download_and_install(pipe_to_blender, package_url: str, install_path: pathlib.Path, search_paths: list):
|
||||||
"""Downloads and installs the given package."""
|
"""Downloads and installs the given package."""
|
||||||
|
|
||||||
from . import cache
|
from . import cache
|
||||||
@@ -530,7 +548,7 @@ def download_and_install(pipe_to_blender, package: dict, install_path: pathlib.P
|
|||||||
log = logging.getLogger('%s.download_and_install' % __name__)
|
log = logging.getLogger('%s.download_and_install' % __name__)
|
||||||
|
|
||||||
cache_dir = cache.cache_directory('downloads')
|
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:
|
if not downloaded:
|
||||||
log.debug('Download failed/aborted, not going to install anything.')
|
log.debug('Download failed/aborted, not going to install anything.')
|
||||||
@@ -538,12 +556,35 @@ def download_and_install(pipe_to_blender, package: dict, install_path: pathlib.P
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
_install(pipe_to_blender, downloaded, install_path, search_paths)
|
_install(pipe_to_blender, downloaded, install_path, search_paths)
|
||||||
_add_to_installed(repo_path, Package.from_dict(package))
|
|
||||||
pipe_to_blender.send(Success())
|
pipe_to_blender.send(Success())
|
||||||
except InstallException as err:
|
except InstallException as err:
|
||||||
log.exception("Failed to install package: %s", err)
|
log.exception("Failed to install package: %s", err)
|
||||||
pipe_to_blender.send(InstallError(err))
|
pipe_to_blender.send(InstallError(err))
|
||||||
|
|
||||||
|
def uninstall(pipe_to_blender, package: Package, install_path: pathlib.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
|
||||||
|
|
||||||
|
#TODO: move this to a shared utility function
|
||||||
|
# Duplicated code with InplaceBackup class
|
||||||
|
def _rm(path: pathlib.Path):
|
||||||
|
"""Just delete whatever is specified by `path`"""
|
||||||
|
if path.is_dir():
|
||||||
|
shutil.rmtree(str(path))
|
||||||
|
else:
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
for pkgfile in [install_path / pathlib.Path(p) for p in package.files]:
|
||||||
|
if not pkgfile.exists():
|
||||||
|
pipe_to_blender.send(UninstallError("Could not find file owned by package: '%s'. Refusing to uninstall." % pkgfile))
|
||||||
|
return None
|
||||||
|
|
||||||
|
for pkgfile in [install_path / pathlib.Path(p) for p in package.files]:
|
||||||
|
_rm(pkgfile)
|
||||||
|
|
||||||
|
pipe_to_blender.send(Success())
|
||||||
|
|
||||||
|
|
||||||
def _load_repo(storage_path: pathlib.Path) -> Repository:
|
def _load_repo(storage_path: pathlib.Path) -> Repository:
|
||||||
"""Reads the stored repositories"""
|
"""Reads the stored repositories"""
|
||||||
|
|
||||||
@@ -571,7 +612,7 @@ def refresh(pipe_to_blender, storage_path: pathlib.Path, repository_url: str):
|
|||||||
return
|
return
|
||||||
|
|
||||||
repo.to_file(repo_path) # TODO: this always writes even if repo wasn't changed
|
repo.to_file(repo_path) # TODO: this always writes even if repo wasn't changed
|
||||||
pipe_to_blender.send(RepositoryResult(repo.to_dict(sort=True, ids=True)))
|
pipe_to_blender.send(RepositoryResult(repo))
|
||||||
pipe_to_blender.send(Success())
|
pipe_to_blender.send(Success())
|
||||||
|
|
||||||
def load(pipe_to_blender, storage_path: pathlib.Path):
|
def load(pipe_to_blender, storage_path: pathlib.Path):
|
||||||
|
Reference in New Issue
Block a user