Basic install function

This commit is contained in:
Ellwood Zwovic
2017-07-11 22:39:31 -07:00
parent f411c68115
commit 7d7be711d3
2 changed files with 118 additions and 7 deletions

View File

@@ -31,6 +31,27 @@ class Progress(SubprocMessage):
def __init__(self, progress: float):
self.progress = progress
class SubprocError(SubprocMessage):
"""Superclass for all fatal error messages sent from the subprocess."""
def __init__(self, message: str):
self.message = message
class SubprocWarning(SubprocMessage):
"""Superclass for all non-fatal warning messages sent from the subprocess."""
def __init__(self, message: str):
self.message = message
class InstallError(SubprocError):
"""Sent when there was an error installing something."""
class FileConflictError(InstallError):
"""Sent when installation would overwrite existing files."""
def __init__(self, message: str, conflicts: list):
self.message = message
self.conflicts = conflicts
class DownloadError(SubprocMessage):
"""Sent when there was an error downloading something."""
@@ -48,6 +69,9 @@ class Aborted(SubprocMessage):
"""Sent as response to Abort message."""
class InstallException(Exception):
"""Raised when there is an error during installation"""
def _download(pipe_to_blender, package_url: str, download_dir: pathlib.Path) -> pathlib.Path:
"""Downloads the given package
@@ -77,6 +101,8 @@ def _download(pipe_to_blender, package_url: str, download_dir: pathlib.Path) ->
log.warning('Server did not send content length, cannot report progress.')
content_length = float('inf')
# TODO: check if there's enough disk space.
# TODO: get filename from Content-Disposition header, if available.
# TODO: use urllib.parse to parse the URL.
local_filename = package_url.split('/')[-1] or 'download.tmp'
@@ -105,11 +131,74 @@ def _download(pipe_to_blender, package_url: str, download_dir: pathlib.Path) ->
# leave 30% "progress" for installation of the package.
pipe_to_blender.send(Progress(downloaded_length / content_length))
pipe_to_blender.send(Success())
return local_fpath
def _install(pipe_to_blender, pkgpath: pathlib.Path, dest: pathlib.Path):
"""Extracts/moves package at `pkgpath` to `dest`"""
import zipfile
def download_and_install(pipe_to_blender, package_url: str):
pipe_to_blender.send(Progress(0.0))
if not pkgpath.is_file():
log.error("File to install isn't a file: %s", pkgpath)
pipe_to_blender.send(InstallError("Package is not a file"))
raise InstallException
if not dest.is_dir():
log.error("Destination for install isn't a directory: %s", dest)
pipe_to_blender.send(InstallError("Destination is not a directory"))
raise InstallException
# TODO: check to make sure addon/package isn't already installed elsewhere
# The following is adapted from `addon_install` in bl_operators/wm.py
# check to see if the file is in compressed format (.zip)
if zipfile.is_zipfile(pkgpath):
try:
file_to_extract = zipfile.ZipFile(str(pkgpath), 'r')
except Exception as err:
pipe_to_blender.send(InstallError("Failed to read zip file: %s" % err))
raise InstallException from err
conflicts = [f for f in file_to_extract.namelist() if (dest / f).exists()]
if len(conflicts) > 0:
# TODO: handle this better than just dumping a list of all files
pipe_to_blender.send(FileConflictError("Installation would overwrite: %s" % conflicts, conflicts))
raise InstallException
try:
file_to_extract.extractall(str(dest))
except Exception as err:
pipe_to_blender.send(InstallError("Failed to extract zip file to '%s': %s" % (dest, err)))
raise InstallException from err
else:
dest_file = (dest / pkgpath.name)
if dest_file.exists():
pipe_to_blender.send(FileConflictError("Installation would overwrite %s" % dest_file, [dest_file]))
raise InstallException
import shutil
try:
shutil.copyfile(str(pkgpath), str(dest_file))
except Exception as err:
pipe_to_blender.send(InstallError("Failed to copy file to '%s': %s" % (dest, err)))
raise InstallException from err
try:
pkgpath.unlink()
except Exception as err:
pipe_to_blender.send(SubprocWarning("Failed to remove package from cache: %s" % err))
raise InstallException from err
pipe_to_blender.send(Progress(1.0))
return
def download_and_install(pipe_to_blender, package_url: str, install_dir: pathlib.Path):
"""Downloads and installs the given package."""
from . import cache
@@ -123,8 +212,13 @@ def download_and_install(pipe_to_blender, package_url: str):
log.debug('Download failed/aborted, not going to install anything.')
return
# TODO: actually install the package
log.warning('Installing is not actually implemented, install %s yourself.', downloaded)
# Only send success if _install doesn't throw an exception
# Maybe not the best way to do this? (will also catch exceptions which occur during message sending)
try:
_install(pipe_to_blender, downloaded, install_dir)
pipe_to_blender.send(Success())
except:
raise
def debug_hang():