diff --git a/bpkg/__init__.py b/bpkg/__init__.py index 490a065..2179722 100644 --- a/bpkg/__init__.py +++ b/bpkg/__init__.py @@ -392,57 +392,117 @@ class USERPREF_PT_packages(bpy.types.Panel): return False - def draw_package(package, layout): + def draw_package(pkg, layout): """Draws the given package""" pkgbox = layout.box() spl = pkgbox.split(.8) - left = spl.column(align=True) + left = spl.row(align=True) + blinfo = pkg['bl_info'] # for install/uninstall buttons right = spl.row() right.alignment = 'RIGHT' - right.scale_y = 2 + right.scale_y = 1.5 - # for title & description - lr1 = left.row() - lr2 = left.row() - lr2.enabled = False #Give name more visual weight + # for collapse/expand button + left.operator( + WM_OT_package_toggle_expand.bl_idname, + icon='TRIA_DOWN' if pkg.get('expand') else 'TRIA_RIGHT', + emboss=False, + ).package_id=pkg['id'] - lr1.label(text=pkg['bl_info'].get('name', "MISSING NAME")) - lr2.label(text=pkg['bl_info'].get('description', "MISSING DESCRIPTION")) + # for metadata + leftcol = left.column(align=True) + + def collapsed(): + lr1 = leftcol.row() + lr2 = leftcol.row() + + lr1.label(text=blinfo.get('name', "MISSING NAME")) + lr2.label(text=blinfo.get('description', "MISSING DESCRIPTION")) + lr2.enabled = False #Give name more visual weight right.operator(BPKG_OT_install.bl_idname, text="Install").package_url=pkg.get('url', "") + def expanded(): + row1 = leftcol.row() + row1.label(blinfo.get('name'), "MISSING NAME") + + 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) + + for prop in ( + # "description", + "author", + "category", + # "version", + # "blender", + "location", + "warning", + "support", + # "wiki_url", + # "tracker_url", + ): + if blinfo.get(prop): + row = leftcol.row() + row.scale_y = .8 + spl = row.split(.15) + spl.label("{}:".format(prop.title())) + spl.label(str(blinfo[prop])) + + + + if pkg.get('expand'): + expanded() + else: + collapsed() + + for pkg in repo['packages']: if filter_package(pkg): row = pkgzone.row() draw_package(pkg, row) -# class WM_OT_package_expand(Operator): -# bl_idname = "wm.package_expand" -# bl_label = "" -# bl_description = "Display information and preferences for this package" -# bl_options = {'INTERNAL'} -# -# module = StringProperty( -# name="Module", -# description="Module name of the add-on to expand", -# ) -# -# def execute(self, context): -# import addon_utils -# -# module_name = self.module -# -# mod = addon_utils.addons_fake_modules.get(module_name) -# if mod is not None: -# info = addon_utils.module_bl_info(mod) -# info["show_expanded"] = not info["show_expanded"] -# -# return {'FINISHED'} -# +class WM_OT_package_toggle_expand(bpy.types.Operator): + bl_idname = "wm.package_toggle_expand" + bl_label = "" + bl_description = "Toggle display of all information for given package" + bl_options = {'INTERNAL'} + + log = logging.getLogger(__name__ + ".WM_OT_package_toggle_expand") + + package_id = bpy.props.StringProperty( + name="Package ID", + description="ID of package to expand/shrink", + ) + + def execute(self, context): + repo = context.user_preferences.addons[__package__].preferences.get('repo') + + if not repo: + return {'CANCELLED'} + + pkg_id = self.package_id + + for pkg in repo['packages']: + if pkg.get('id') == pkg_id: + # if pkg['expand'] is unset, it's not expanded + pkg['expand'] = not pkg.get('expand', False) + + return {'FINISHED'} + class PackageManagerPreferences(bpy.types.AddonPreferences): bl_idname = __package__ @@ -469,6 +529,7 @@ def register(): bpy.utils.register_class(BPKG_OT_refresh) bpy.utils.register_class(BPKG_OT_hang) bpy.utils.register_class(USERPREF_PT_packages) + bpy.utils.register_class(WM_OT_package_toggle_expand) bpy.types.WindowManager.package_search = bpy.props.StringProperty( name="Search", description="Filter packages by name", @@ -490,6 +551,7 @@ def unregister(): bpy.utils.unregister_class(BPKG_OT_refresh) bpy.utils.unregister_class(BPKG_OT_hang) bpy.utils.unregister_class(USERPREF_PT_packages) + bpy.utils.unregister_class(WM_OT_package_toggle_expand) del bpy.types.WindowManager.package_search del bpy.types.WindowManager.package_install_filter bpy.utils.unregister_class(PackageManagerPreferences) diff --git a/bpkg/subproc.py b/bpkg/subproc.py index cf07d17..18ee0bd 100644 --- a/bpkg/subproc.py +++ b/bpkg/subproc.py @@ -175,8 +175,11 @@ class Repository: log = logging.getLogger(__name__ + ".Repository") def __init__(self, url=None): + if url is None: + url = "" self.set_from_dict({'url': url}) self.log.debug("Initializing repository: %s", self.to_dict()) + self.log.debug("Own URL is '%s'", self.url) # def cleanse_packagelist(self): # """Remove empty packages (no bl_info), packages with no name""" @@ -241,14 +244,21 @@ class Repository: self.set_from_dict(repodict) - def to_dict(self, sort=False) -> dict: + def to_dict(self, sort=False, ids=False) -> dict: """ Return a dict representation of the repository """ packages = [p.to_dict() for p in self.packages] + if sort: packages.sort(key=lambda p: p['bl_info']['name'].lower()) + if ids: + for pkg in packages: + self.log.debug(pkg['url'], pkg['bl_info']['name'], self.name, self.url) + # hash may be too big for a C int + pkg['id'] = str(hash(pkg['url'] + pkg['bl_info']['name'] + self.name + self.url)) + return { 'name': self.name, 'packages': packages, @@ -260,10 +270,23 @@ class Repository: """ Get repository attributes from a dict such as produced by `to_dict` """ - self.name = repodict.get('name', "") - self.url = repodict.get('url', "") - self.packages = [Package(pkg) for pkg in repodict.get('packages', [])] - self._headers = repodict.get('_headers', {}) + + def initialize(item, value): + if item is None: + return value + else: + return item + + #Be certain to initialize everything; downloaded packagelist might contain null values + name = initialize(repodict.get('name'), "") + url = initialize(repodict.get('url'), "") + packages = initialize(repodict.get('packages'), []) + headers = initialize(repodict.get('_headers'), {}) + + self.name = name + self.url = url + self.packages = [Package(pkg) for pkg in packages] + self._headers = headers @classmethod def from_dict(cls, repodict: dict): @@ -466,6 +489,12 @@ def download_and_install(pipe_to_blender, package_url: str, install_path: pathli log.exception("Failed to install package: %s", err) pipe_to_blender.send(InstallError(err)) +def _load_repo(storage_path: pathlib.Path) -> Repository: + """Reads the stored repositories""" + + repo_path = storage_path / 'repo.json' + return Repository.from_file(repo_path) + def refresh(pipe_to_blender, storage_path: pathlib.Path, repository_url: str): """Retrieves and stores the given repository""" @@ -489,10 +518,17 @@ 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))) + pipe_to_blender.send(RepositoryResult(repo.to_dict(sort=True, ids=True))) def load(pipe_to_blender, storage_path: pathlib.Path): - """Reads the stored repository""" + """Reads the stored repository and sends the result to blender""" + + try: + repo = _load_repo(storage_path) + pipe_to_blender.send(RepositoryResult(repo.to_dict(sort=True, ids=True))) + except BadRepository as err: + pipe_to_blender.send(SubprocError("Failed to read repository: %s" % err)) + def debug_hang(): """Hangs for an hour. For testing purposes only."""