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:
@@ -48,6 +48,9 @@ class SubprocWarning(SubprocMessage):
|
||||
class InstallError(SubprocError):
|
||||
"""Sent when there was an error installing something."""
|
||||
|
||||
class UninstallError(SubprocError):
|
||||
"""Sent when there was an error uninstalling something."""
|
||||
|
||||
class FileConflictError(InstallError):
|
||||
"""Sent when installation would overwrite existing files."""
|
||||
|
||||
@@ -149,6 +152,9 @@ class Package:
|
||||
self.files = []
|
||||
self.set_from_dict(package_dict)
|
||||
|
||||
self.installed = False
|
||||
self.repository = None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""
|
||||
Return a dict representation of the package
|
||||
@@ -171,14 +177,21 @@ class Package:
|
||||
setattr(self, attr, package_dict[attr])
|
||||
|
||||
#bl_info convenience getters
|
||||
def get_name() -> str:
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Get name from bl_info"""
|
||||
return self.bl_info['name']
|
||||
|
||||
def get_description() -> str:
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""Get description from bl_info"""
|
||||
return self.bl_info['description']
|
||||
|
||||
@property
|
||||
def version(self) -> tuple:
|
||||
"""Get version from bl_info"""
|
||||
return tuple(self.bl_info['version'])
|
||||
|
||||
# @classmethod
|
||||
# def from_dict(cls, package_dict: dict):
|
||||
# """
|
||||
@@ -205,7 +218,8 @@ class Package:
|
||||
filepath = filepath.parent
|
||||
|
||||
pkg = cls()
|
||||
pkg.files = [filepath]
|
||||
pkg.files = [filepath.name]
|
||||
pkg.installed_location = str(filepath)
|
||||
try:
|
||||
pkg.bl_info = module.bl_info
|
||||
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))
|
||||
|
||||
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:
|
||||
resp.raise_for_status()
|
||||
@@ -522,7 +540,7 @@ def _install(pipe_to_blender, pkgpath: pathlib.Path, dest: pathlib.Path, searchp
|
||||
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."""
|
||||
|
||||
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__)
|
||||
|
||||
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.')
|
||||
@@ -538,12 +556,35 @@ def download_and_install(pipe_to_blender, package: dict, install_path: pathlib.P
|
||||
|
||||
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)
|
||||
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:
|
||||
"""Reads the stored repositories"""
|
||||
|
||||
@@ -571,7 +612,7 @@ def refresh(pipe_to_blender, storage_path: pathlib.Path, repository_url: str):
|
||||
return
|
||||
|
||||
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())
|
||||
|
||||
def load(pipe_to_blender, storage_path: pathlib.Path):
|
||||
|
Reference in New Issue
Block a user