From 0d6a5064476c9dbdb003b04f5a343a5e34c2c25c Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 27 Feb 2022 22:50:54 -0600 Subject: [PATCH] Addon Manager: Renaming and cleanup --- .../{AddonManagerRepo.py => Addon.py} | 42 +++--- src/Mod/AddonManager/AddonManager.py | 100 +++++++------- .../AddonManagerTest/app/test_utilities.py | 18 +-- src/Mod/AddonManager/CMakeLists.txt | 2 +- src/Mod/AddonManager/Init.py | 3 +- src/Mod/AddonManager/InitGui.py | 3 +- src/Mod/AddonManager/NetworkManager.py | 41 +++--- src/Mod/AddonManager/TestAddonManagerApp.py | 4 +- .../AddonManager/addonmanager_utilities.py | 9 +- src/Mod/AddonManager/addonmanager_workers.py | 130 ++++++++---------- src/Mod/AddonManager/change_branch.py | 2 +- src/Mod/AddonManager/install_to_toolbar.py | 12 +- src/Mod/AddonManager/package_details.py | 92 +++++++------ src/Mod/AddonManager/package_list.py | 78 ++++++----- 14 files changed, 274 insertions(+), 262 deletions(-) rename src/Mod/AddonManager/{AddonManagerRepo.py => Addon.py} (92%) diff --git a/src/Mod/AddonManager/AddonManagerRepo.py b/src/Mod/AddonManager/Addon.py similarity index 92% rename from src/Mod/AddonManager/AddonManagerRepo.py rename to src/Mod/AddonManager/Addon.py index 548e30327b..c27a6ca8ba 100644 --- a/src/Mod/AddonManager/AddonManagerRepo.py +++ b/src/Mod/AddonManager/Addon.py @@ -1,6 +1,6 @@ # *************************************************************************** # * * -# * Copyright (c) 2021 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -33,12 +33,12 @@ import addonmanager_utilities as utils translate = FreeCAD.Qt.translate -class AddonManagerRepo: +class Addon: "Encapsulate information about a FreeCAD addon" from enum import IntEnum - class RepoType(IntEnum): + class Kind(IntEnum): WORKBENCH = 1 MACRO = 2 PACKAGE = 3 @@ -51,7 +51,7 @@ class AddonManagerRepo: elif self.value == 3: return "Package" - class UpdateStatus(IntEnum): + class Status(IntEnum): NOT_INSTALLED = 0 UNCHECKED = 1 NO_UPDATE_AVAILABLE = 2 @@ -91,7 +91,7 @@ class AddonManagerRepo: def __init__(self, msg): super().__init__(msg) - def __init__(self, name: str, url: str, status: UpdateStatus, branch: str): + def __init__(self, name: str, url: str, status: Status, branch: str): self.name = name.strip() self.display_name = self.name self.url = url.strip() @@ -99,7 +99,7 @@ class AddonManagerRepo: self.python2 = False self.obsolete = False self.rejected = False - self.repo_type = AddonManagerRepo.RepoType.WORKBENCH + self.repo_type = Addon.Kind.WORKBENCH self.description = None self.tags = set() # Just a cache, loaded from Metadata @@ -155,12 +155,12 @@ class AddonManagerRepo: @classmethod def from_macro(self, macro: Macro): if macro.is_installed(): - status = AddonManagerRepo.UpdateStatus.UNCHECKED + status = Addon.Status.UNCHECKED else: - status = AddonManagerRepo.UpdateStatus.NOT_INSTALLED - instance = AddonManagerRepo(macro.name, macro.url, status, "master") + status = Addon.Status.NOT_INSTALLED + instance = Addon(macro.name, macro.url, status, "master") instance.macro = macro - instance.repo_type = AddonManagerRepo.RepoType.MACRO + instance.repo_type = Addon.Kind.MACRO instance.description = macro.desc return instance @@ -170,18 +170,18 @@ class AddonManagerRepo: mod_dir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", cache_dict["name"]) if os.path.isdir(mod_dir): - status = AddonManagerRepo.UpdateStatus.UNCHECKED + status = Addon.Status.UNCHECKED else: - status = AddonManagerRepo.UpdateStatus.NOT_INSTALLED - instance = AddonManagerRepo( + status = Addon.Status.NOT_INSTALLED + instance = Addon( cache_dict["name"], cache_dict["url"], status, cache_dict["branch"] ) for key, value in cache_dict.items(): instance.__dict__[key] = value - instance.repo_type = AddonManagerRepo.RepoType(cache_dict["repo_type"]) - if instance.repo_type == AddonManagerRepo.RepoType.PACKAGE: + instance.repo_type = Addon.Kind(cache_dict["repo_type"]) + if instance.repo_type == Addon.Kind.PACKAGE: # There must be a cached metadata file, too cached_package_xml_file = os.path.join( FreeCAD.getUserCachePath(), @@ -232,7 +232,7 @@ class AddonManagerRepo: def set_metadata(self, metadata: FreeCAD.Metadata) -> None: self.metadata = metadata self.display_name = metadata.Name - self.repo_type = AddonManagerRepo.RepoType.PACKAGE + self.repo_type = Addon.Kind.PACKAGE self.description = metadata.Description for url in metadata.Urls: if "type" in url and url["type"] == "repository": @@ -277,9 +277,9 @@ class AddonManagerRepo: def contains_workbench(self) -> bool: """Determine if this package contains (or is) a workbench""" - if self.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + if self.repo_type == Addon.Kind.WORKBENCH: return True - elif self.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif self.repo_type == Addon.Kind.PACKAGE: if self.metadata is None: FreeCAD.Console.PrintWarning( f"Addon Manager internal error: lost metadata for package {self.name}\n" @@ -298,9 +298,9 @@ class AddonManagerRepo: def contains_macro(self) -> bool: """Determine if this package contains (or is) a macro""" - if self.repo_type == AddonManagerRepo.RepoType.MACRO: + if self.repo_type == Addon.Kind.MACRO: return True - elif self.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif self.repo_type == Addon.Kind.PACKAGE: if self.metadata is None: FreeCAD.Console.PrintWarning( f"Addon Manager internal error: lost metadata for package {self.name}\n" @@ -314,7 +314,7 @@ class AddonManagerRepo: def contains_preference_pack(self) -> bool: """Determine if this package contains a preference pack""" - if self.repo_type == AddonManagerRepo.RepoType.PACKAGE: + if self.repo_type == Addon.Kind.PACKAGE: if self.metadata is None: FreeCAD.Console.PrintWarning( f"Addon Manager internal error: lost metadata for package {self.name}\n" diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 49ea30ace8..1cdc3043d4 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -4,7 +4,7 @@ # *************************************************************************** # * * # * Copyright (c) 2015 Yorik van Havre * -# * Copyright (c) 2021 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -40,7 +40,7 @@ import addonmanager_utilities as utils import AddonManager_rc from package_list import PackageList, PackageListItemModel from package_details import PackageDetails -from AddonManagerRepo import AddonManagerRepo +from Addon import Addon from install_to_toolbar import ( ask_to_install_toolbar_button, remove_custom_toolbar_button, @@ -496,7 +496,7 @@ class CommandAddonManager: # Write the cache data if it's safe to do so: if not worker_killed: for repo in self.item_model.repos: - if repo.repo_type == AddonManagerRepo.RepoType.MACRO: + if repo.repo_type == Addon.Kind.MACRO: self.cache_macro(repo) else: self.cache_package(repo) @@ -633,7 +633,7 @@ class CommandAddonManager: ) # Link to step 2 self.update_worker.start() - def cache_package(self, repo: AddonManagerRepo): + def cache_package(self, repo: Addon): if not hasattr(self, "package_cache"): self.package_cache = {} self.package_cache[repo.name] = repo.to_cache() @@ -671,7 +671,7 @@ class CommandAddonManager: self.macro_worker.finished.connect(self.do_next_startup_phase) self.macro_worker.start() - def cache_macro(self, repo: AddonManagerRepo): + def cache_macro(self, repo: Addon): if not hasattr(self, "macro_cache"): self.macro_cache = [] if repo.macro is not None: @@ -724,7 +724,7 @@ class CommandAddonManager: ) self.startup() - def on_package_updated(self, repo: AddonManagerRepo) -> None: + def on_package_updated(self, repo: Addon) -> None: """Called when the named package has either new metadata or a new icon (or both)""" with self.lock: @@ -788,12 +788,12 @@ class CommandAddonManager: self.check_worker.start() self.enable_updates(len(self.packages_with_updates)) - def status_updated(self, repo: AddonManagerRepo) -> None: + def status_updated(self, repo: Addon) -> None: self.item_model.reload_item(repo) - if repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + if repo.status() == Addon.Status.UPDATE_AVAILABLE: self.packages_with_updates.append(repo) self.enable_updates(len(self.packages_with_updates)) - elif repo.status() == AddonManagerRepo.UpdateStatus.PENDING_RESTART: + elif repo.status() == Addon.Status.PENDING_RESTART: self.restart_required = True def enable_updates(self, number_of_updates: int) -> None: @@ -819,7 +819,7 @@ class CommandAddonManager: self.enable_updates(len(self.packages_with_updates)) self.dialog.buttonCheckForUpdates.setEnabled(True) - def add_addon_repo(self, addon_repo: AddonManagerRepo) -> None: + def add_addon_repo(self, addon_repo: Addon) -> None: """adds a workbench to the list""" if addon_repo.icon is None or addon_repo.icon.isNull(): @@ -832,17 +832,17 @@ class CommandAddonManager: return self.item_model.append_item(addon_repo) - def get_icon(self, repo: AddonManagerRepo, update: bool = False) -> QtGui.QIcon: + def get_icon(self, repo: Addon, update: bool = False) -> QtGui.QIcon: """returns an icon for a repo""" if not update and repo.icon and not repo.icon.isNull() and repo.icon.isValid(): return repo.icon path = ":/icons/" + repo.name.replace(" ", "_") - if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + if repo.repo_type == Addon.Kind.WORKBENCH: path += "_workbench_icon.svg" default_icon = QtGui.QIcon(":/icons/document-package.svg") - elif repo.repo_type == AddonManagerRepo.RepoType.MACRO: + elif repo.repo_type == Addon.Kind.MACRO: if repo.macro and repo.macro.icon: if os.path.isabs(repo.macro.icon): path = repo.macro.icon @@ -864,7 +864,7 @@ class CommandAddonManager: else: path += "_macro_icon.svg" default_icon = QtGui.QIcon(":/icons/document-python.svg") - elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif repo.repo_type == Addon.Kind.PACKAGE: # The cache might not have been downloaded yet, check to see if it's there... if os.path.isfile(repo.get_cached_icon_filename()): path = repo.get_cached_icon_filename() @@ -885,7 +885,7 @@ class CommandAddonManager: return addonicon - def table_row_activated(self, selected_repo: AddonManagerRepo) -> None: + def table_row_activated(self, selected_repo: Addon) -> None: """a row was activated, show the relevant data""" self.packageList.hide() @@ -898,7 +898,7 @@ class CommandAddonManager: self.dialog.labelStatusInfo.setText(message) self.dialog.labelStatusInfo.repaint() - def show_workbench(self, repo: AddonManagerRepo) -> None: + def show_workbench(self, repo: Addon) -> None: self.packageList.hide() self.packageDetails.show() self.packageDetails.show_repo(repo) @@ -907,16 +907,16 @@ class CommandAddonManager: self.packageDetails.hide() self.packageList.show() - def append_to_repos_list(self, repo: AddonManagerRepo) -> None: + def append_to_repos_list(self, repo: Addon) -> None: """this function allows threads to update the main list of workbenches""" self.item_model.append_item(repo) - def resolve_dependencies(self, repo: AddonManagerRepo) -> None: + def resolve_dependencies(self, repo: Addon) -> None: if not repo: return - deps = AddonManagerRepo.Dependencies() + deps = Addon.Dependencies() repo_name_dict = dict() for r in self.item_model.repos: repo_name_dict[repo.name] = r @@ -941,7 +941,7 @@ class CommandAddonManager: missing_external_addons = [] for dep in deps.required_external_addons: - if dep.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if dep.status() == Addon.Status.NOT_INSTALLED: missing_external_addons.append(dep) # Now check the loaded addons to see if we are missing an internal workbench: @@ -1069,7 +1069,7 @@ class CommandAddonManager: else: self.install(repo) - def dependency_dialog_yes_clicked(self, repo: AddonManagerRepo) -> None: + def dependency_dialog_yes_clicked(self, repo: Addon) -> None: # Get the lists out of the dialog: addons = [] for row in range(self.dependency_dialog.listWidgetAddons.count()): @@ -1116,7 +1116,7 @@ class CommandAddonManager: self.dependency_installation_dialog.show() self.dependency_installation_worker.start() - def no_python_exe(self, repo: AddonManagerRepo) -> None: + def no_python_exe(self, repo: Addon) -> None: if hasattr(self, "dependency_installation_dialog"): self.dependency_installation_dialog.hide() result = QtWidgets.QMessageBox.critical( @@ -1136,7 +1136,7 @@ class CommandAddonManager: if result == QtWidgets.QMessageBox.Yes: self.install(repo) - def no_pip(self, command: str, repo: AddonManagerRepo) -> None: + def no_pip(self, command: str, repo: Addon) -> None: if hasattr(self, "dependency_installation_dialog"): self.dependency_installation_dialog.hide() result = QtWidgets.QMessageBox.critical( @@ -1169,7 +1169,7 @@ class CommandAddonManager: QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, ) - def dependency_dialog_ignore_clicked(self, repo: AddonManagerRepo) -> None: + def dependency_dialog_ignore_clicked(self, repo: Addon) -> None: self.install(repo) def cancel_dependency_installation(self) -> None: @@ -1177,7 +1177,7 @@ class CommandAddonManager: self.dependency_installation_worker.requestInterruption() self.dependency_installation_dialog.hide() - def install(self, repo: AddonManagerRepo) -> None: + def install(self, repo: Addon) -> None: """installs or updates a workbench, macro, or package""" if hasattr(self, "install_worker") and self.install_worker: @@ -1191,8 +1191,8 @@ class CommandAddonManager: return if ( - repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH - or repo.repo_type == AddonManagerRepo.RepoType.PACKAGE + repo.repo_type == Addon.Kind.WORKBENCH + or repo.repo_type == Addon.Kind.PACKAGE ): self.show_progress_widgets() self.install_worker = InstallWorkbenchWorker(repo) @@ -1203,7 +1203,7 @@ class CommandAddonManager: self.install_worker.success.connect(self.on_package_installed) self.install_worker.failure.connect(self.on_installation_failed) self.install_worker.start() - elif repo.repo_type == AddonManagerRepo.RepoType.MACRO: + elif repo.repo_type == Addon.Kind.MACRO: macro = repo.macro # To try to ensure atomicity, test the installation into a temp directory first, @@ -1238,16 +1238,14 @@ class CommandAddonManager: message += error self.on_installation_failed(repo, message) - def update(self, repo: AddonManagerRepo) -> None: + def update(self, repo: Addon) -> None: self.install(repo) - def mark_repo_update_available( - self, repo: AddonManagerRepo, available: bool - ) -> None: + def mark_repo_update_available(self, repo: Addon, available: bool) -> None: if available: - repo.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE) + repo.set_status(Addon.Status.UPDATE_AVAILABLE) else: - repo.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE) + repo.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.item_model.reload_item(repo) self.packageDetails.show_repo(repo) @@ -1279,9 +1277,7 @@ class CommandAddonManager: def on_update_all_completed(self) -> None: self.hide_progress_widgets() - def get_package_list( - message: str, repos: List[AddonManagerRepo], threshold: int - ): + def get_package_list(message: str, repos: List[Addon], threshold: int): """To ensure that the list doesn't get too long for the dialog, cut it off at some threshold""" num_updates = len(repos) if num_updates < threshold: @@ -1339,11 +1335,9 @@ class CommandAddonManager: for installed_repo in self.subupdates_succeeded: if installed_repo.contains_workbench(): self.restart_required = True - installed_repo.set_status(AddonManagerRepo.UpdateStatus.PENDING_RESTART) + installed_repo.set_status(Addon.Status.PENDING_RESTART) else: - installed_repo.set_status( - AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE - ) + installed_repo.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.item_model.reload_item(installed_repo) for requested_repo in self.packages_with_updates: if installed_repo.name == requested_repo.name: @@ -1407,7 +1401,7 @@ class CommandAddonManager: "AddonManager recaches." ) - def on_package_installed(self, repo: AddonManagerRepo, message: str) -> None: + def on_package_installed(self, repo: Addon, message: str) -> None: self.hide_progress_widgets() QtWidgets.QMessageBox.information( None, @@ -1416,16 +1410,16 @@ class CommandAddonManager: QtWidgets.QMessageBox.Close, ) if repo.contains_workbench(): - repo.set_status(AddonManagerRepo.UpdateStatus.PENDING_RESTART) + repo.set_status(Addon.Status.PENDING_RESTART) self.restart_required = True else: - repo.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE) + repo.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.item_model.reload_item(repo) self.packageDetails.show_repo(repo) - if repo.repo_type == AddonManagerRepo.RepoType.MACRO: + if repo.repo_type == Addon.Kind.MACRO: ask_to_install_toolbar_button(repo) - def on_installation_failed(self, _: AddonManagerRepo, message: str) -> None: + def on_installation_failed(self, _: Addon, message: str) -> None: self.hide_progress_widgets() QtWidgets.QMessageBox.warning( None, @@ -1434,7 +1428,7 @@ class CommandAddonManager: QtWidgets.QMessageBox.Close, ) - def executemacro(self, repo: AddonManagerRepo) -> None: + def executemacro(self, repo: Addon) -> None: """executes a selected macro""" macro = repo.macro @@ -1468,7 +1462,7 @@ class CommandAddonManager: os.chmod(path, stat.S_IWRITE) func(path) - def remove(self, repo: AddonManagerRepo) -> None: + def remove(self, repo: Addon) -> None: """uninstalls a macro or workbench""" confirm = QtWidgets.QMessageBox.question( @@ -1483,8 +1477,8 @@ class CommandAddonManager: return if ( - repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH - or repo.repo_type == AddonManagerRepo.RepoType.PACKAGE + repo.repo_type == Addon.Kind.WORKBENCH + or repo.repo_type == Addon.Kind.PACKAGE ): basedir = FreeCAD.getUserAppDataDir() moddir = basedir + os.sep + "Mod" @@ -1535,7 +1529,7 @@ class CommandAddonManager: if os.path.exists(clonedir): shutil.rmtree(clonedir, onerror=self.remove_readonly) self.item_model.update_item_status( - repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED + repo.name, Addon.Status.NOT_INSTALLED ) if repo.contains_workbench(): self.restart_required = True @@ -1548,7 +1542,7 @@ class CommandAddonManager: ) ) - elif repo.repo_type == AddonManagerRepo.RepoType.MACRO: + elif repo.repo_type == Addon.Kind.MACRO: macro = repo.macro if macro.remove(): remove_custom_toolbar_button(repo) @@ -1559,7 +1553,7 @@ class CommandAddonManager: + "\n" ) self.item_model.update_item_status( - repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED + repo.name, Addon.Status.NOT_INSTALLED ) self.packageDetails.show_repo(repo) else: diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_utilities.py b/src/Mod/AddonManager/AddonManagerTest/app/test_utilities.py index 1cb3fc6f42..b714ca8f57 100644 --- a/src/Mod/AddonManager/AddonManagerTest/app/test_utilities.py +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_utilities.py @@ -26,7 +26,7 @@ import unittest import os import FreeCAD -from AddonManagerRepo import AddonManagerRepo +from Addon import Addon from addonmanager_utilities import ( recognized_git_location, @@ -51,8 +51,8 @@ class TestUtilities(unittest.TestCase): "https://salsa.debian.org/science-team/freecad", ] for url in recognized_urls: - repo = AddonManagerRepo( - "Test Repo", url, AddonManagerRepo.UpdateStatus.NOT_INSTALLED, "branch" + repo = Addon( + "Test Repo", url, Addon.Status.NOT_INSTALLED, "branch" ) self.assertTrue( recognized_git_location(repo), f"{url} was unexpectedly not recognized" @@ -65,8 +65,8 @@ class TestUtilities(unittest.TestCase): "https://github.com.malware.com/", ] for url in unrecognized_urls: - repo = AddonManagerRepo( - "Test Repo", url, AddonManagerRepo.UpdateStatus.NOT_INSTALLED, "branch" + repo = Addon( + "Test Repo", url, Addon.Status.NOT_INSTALLED, "branch" ) self.assertFalse( recognized_git_location(repo), f"{url} was unexpectedly recognized" @@ -90,8 +90,8 @@ class TestUtilities(unittest.TestCase): for url in github_urls: branch = "branchname" expected_result = f"{url}/raw/{branch}/README.md" - repo = AddonManagerRepo( - "Test Repo", url, AddonManagerRepo.UpdateStatus.NOT_INSTALLED, branch + repo = Addon( + "Test Repo", url, Addon.Status.NOT_INSTALLED, branch ) actual_result = get_readme_url(repo) self.assertEqual(actual_result, expected_result) @@ -99,8 +99,8 @@ class TestUtilities(unittest.TestCase): for url in gitlab_urls: branch = "branchname" expected_result = f"{url}/-/raw/{branch}/README.md" - repo = AddonManagerRepo( - "Test Repo", url, AddonManagerRepo.UpdateStatus.NOT_INSTALLED, branch + repo = Addon( + "Test Repo", url, Addon.Status.NOT_INSTALLED, branch ) actual_result = get_readme_url(repo) self.assertEqual(actual_result, expected_result) diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index 928b9c752b..108974ad08 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -4,13 +4,13 @@ ENDIF (BUILD_GUI) SET(AddonManager_SRCS add_toolbar_button_dialog.ui + Addon.py AddonManager.py AddonManager.ui addonmanager_macro.py addonmanager_utilities.py addonmanager_workers.py AddonManagerOptions.ui - AddonManagerRepo.py ALLOWED_PYTHON_PACKAGES.txt change_branch.py change_branch.ui diff --git a/src/Mod/AddonManager/Init.py b/src/Mod/AddonManager/Init.py index 5a1ad04997..7411e7f661 100644 --- a/src/Mod/AddonManager/Init.py +++ b/src/Mod/AddonManager/Init.py @@ -4,4 +4,5 @@ # License LGPL import FreeCAD -FreeCAD.__unit_test__ += ["TestAddonManagerApp"] \ No newline at end of file + +FreeCAD.__unit_test__ += ["TestAddonManagerApp"] diff --git a/src/Mod/AddonManager/InitGui.py b/src/Mod/AddonManager/InitGui.py index 4436af989c..7bec7cb34c 100644 --- a/src/Mod/AddonManager/InitGui.py +++ b/src/Mod/AddonManager/InitGui.py @@ -9,4 +9,5 @@ FreeCADGui.addLanguagePath(":/translations") FreeCADGui.addCommand("Std_AddonMgr", AddonManager.CommandAddonManager()) import FreeCAD -FreeCAD.__unit_test__ += ["TestAddonManagerGui"] \ No newline at end of file + +FreeCAD.__unit_test__ += ["TestAddonManagerGui"] diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index fca83600d6..625249e032 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * -# * Copyright (c) 2022 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -163,7 +163,7 @@ if HAVE_QTNETWORK: self.diskCache.setCacheDirectory(qnam_cache) self.QNAM.setCache(self.diskCache) - self.monitored_connections:List[int] = [] + self.monitored_connections: List[int] = [] # Set up the proxy, if necesssary: noProxyCheck = True @@ -269,7 +269,9 @@ if HAVE_QTNETWORK: except queue.Empty: pass - def __launch_request(self, index:int, request:QtNetwork.QNetworkRequest) -> None: + def __launch_request( + self, index: int, request: QtNetwork.QNetworkRequest + ) -> None: reply = self.QNAM.get(request) self.replies[index] = reply @@ -281,7 +283,6 @@ if HAVE_QTNETWORK: reply.readyRead.connect(self.__ready_to_read) reply.downloadProgress.connect(self.__download_progress) - def submit_unmonitored_get(self, url: str) -> int: """Adds this request to the queue, and returns an index that can be used by calling code in conjunction with the completed() signal to handle the results of the call. All data is @@ -437,7 +438,7 @@ if HAVE_QTNETWORK: def __follow_redirect(self, url): sender = self.sender() if sender: - for index,reply in self.replies.items(): + for index, reply in self.replies.items(): if reply == sender: current_index = index break @@ -459,11 +460,11 @@ if HAVE_QTNETWORK: for error in errors: print(error) - def __download_progress(self, bytesReceived:int, bytesTotal:int) -> None: + def __download_progress(self, bytesReceived: int, bytesTotal: int) -> None: sender = self.sender() if not sender: return - for index,reply in self.replies.items(): + for index, reply in self.replies.items(): if reply == sender: self.progress_made.emit(index, bytesReceived, bytesTotal) return @@ -473,12 +474,12 @@ if HAVE_QTNETWORK: if not sender: return - for index,reply in self.replies.items(): + for index, reply in self.replies.items(): if reply == sender: self.__data_incoming(index, reply) return - def __data_incoming(self, index: int, reply:QtNetwork.QNetworkReply) -> None: + def __data_incoming(self, index: int, reply: QtNetwork.QNetworkReply) -> None: if not index in self.replies: # We already finished this reply, this is a vestigial signal return @@ -492,28 +493,34 @@ if HAVE_QTNETWORK: f.write(buffer.data()) except Exception as e: if HAVE_FREECAD: - FreeCAD.Console.PrintError(f"Network Manager internal error: {str(e)}") + FreeCAD.Console.PrintError( + f"Network Manager internal error: {str(e)}" + ) else: - print (f"Network Manager internal error: {str(e)}") - + print(f"Network Manager internal error: {str(e)}") def __reply_finished(self) -> None: reply = self.sender() if not reply: - print ("Network Manager Error: __reply_finished not called by a Qt signal") + print( + "Network Manager Error: __reply_finished not called by a Qt signal" + ) return - if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: + if ( + reply.error() + == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError + ): # Silently do nothing return index = None - for key,value in self.replies.items(): + for key, value in self.replies.items(): if reply == value: index = key break if index is None: - print (f"Lost net request for {reply.url()}") + print(f"Lost net request for {reply.url()}") return response_code = reply.attribute( @@ -522,7 +529,7 @@ if HAVE_QTNETWORK: self.queue.task_done() if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: if index in self.monitored_connections: - # Make sure to read any remaining data + # Make sure to read any remaining data self.__data_incoming(index, reply) self.monitored_connections.remove(index) f = self.file_buffers[index] diff --git a/src/Mod/AddonManager/TestAddonManagerApp.py b/src/Mod/AddonManager/TestAddonManagerApp.py index 1ab74a1421..bfc05ab562 100644 --- a/src/Mod/AddonManager/TestAddonManagerApp.py +++ b/src/Mod/AddonManager/TestAddonManagerApp.py @@ -23,7 +23,9 @@ # *************************************************************************** # Unit test for the Addon Manager module -from AddonManagerTest.app.test_utilities import TestUtilities as AddonManagerTestUtilities +from AddonManagerTest.app.test_utilities import ( + TestUtilities as AddonManagerTestUtilities, +) # dummy usage to get flake8 and lgtm quiet False if AddonManagerTestUtilities.__name__ else True diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index f7ca33a9d7..52e255c6d6 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -276,13 +276,13 @@ def attention_color_string() -> str: return "rgb(255,179,64)" if is_darkmode() else "rgb(255,149,0)" -def get_assigned_string_literal(line:str) -> Optional[str]: +def get_assigned_string_literal(line: str) -> Optional[str]: """Look for a line of the form my_var = "A string literal" and return the string literal. If the assignment is of a floating point value, that value is converted to a string and returned. If neither is true, returns None.""" string_search_regex = re.compile(r"\s*(['\"])(.*)\1") - _,_,after_equals = line.partition("=") + _, _, after_equals = line.partition("=") match = re.match(string_search_regex, after_equals) if match: return str(match.group(2)) @@ -290,9 +290,10 @@ def get_assigned_string_literal(line:str) -> Optional[str]: return str(after_equals).strip() return None + def get_macro_version_from_file(filename: str) -> str: - """ Get the version of the macro from a local macro file. Supports strings, ints, and floats, as - well as a reference to __date__ """ + """Get the version of the macro from a local macro file. Supports strings, ints, and floats, as + well as a reference to __date__""" date = "" with open(filename, "r", errors="ignore") as f: diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 18af01564b..127be729db 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * # * Copyright (c) 2019 Yorik van Havre * -# * Copyright (c) 2021 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -51,7 +51,7 @@ if FreeCAD.GuiUp: import addonmanager_utilities as utils from addonmanager_macro import Macro -from AddonManagerRepo import AddonManagerRepo +from Addon import Addon import NetworkManager translate = FreeCAD.Qt.translate @@ -239,10 +239,10 @@ class UpdateWorker(QtCore.QThread): package_names.append(name) addondir = moddir + os.sep + name if os.path.exists(addondir) and os.listdir(addondir): - state = AddonManagerRepo.UpdateStatus.UNCHECKED + state = Addon.Status.UNCHECKED else: - state = AddonManagerRepo.UpdateStatus.NOT_INSTALLED - repo = AddonManagerRepo(name, addon["url"], state, addon["branch"]) + state = Addon.Status.NOT_INSTALLED + repo = Addon(name, addon["url"], state, addon["branch"]) md_file = os.path.join(addondir, "package.xml") if os.path.isfile(md_file): repo.load_metadata_file(md_file) @@ -281,10 +281,10 @@ class UpdateWorker(QtCore.QThread): addondir = moddir + os.sep + name if os.path.exists(addondir) and os.listdir(addondir): # make sure the folder exists and it contains files! - state = AddonManagerRepo.UpdateStatus.UNCHECKED + state = Addon.Status.UNCHECKED else: - state = AddonManagerRepo.UpdateStatus.NOT_INSTALLED - repo = AddonManagerRepo(name, url, state, branch) + state = Addon.Status.NOT_INSTALLED + repo = Addon(name, url, state, branch) md_file = os.path.join(addondir, "package.xml") if os.path.isfile(md_file): repo.load_metadata_file(md_file) @@ -323,7 +323,7 @@ class LoadPackagesFromCacheWorker(QtCore.QThread): for item in dict_data.values(): if QtCore.QThread.currentThread().isInterruptionRequested(): return - repo = AddonManagerRepo.from_cache(item) + repo = Addon.from_cache(item) repo_metadata_cache_path = os.path.join( metadata_cache_path, repo.name, "package.xml" ) @@ -357,7 +357,7 @@ class LoadMacrosFromCacheWorker(QtCore.QThread): if QtCore.QThread.currentThread().isInterruptionRequested(): return new_macro = Macro.from_cache(item) - repo = AddonManagerRepo.from_macro(new_macro) + repo = Addon.from_macro(new_macro) utils.update_macro_installation_details(repo) self.add_macro_signal.emit(repo) @@ -368,18 +368,18 @@ class CheckSingleUpdateWorker(QtCore.QObject): update_status = QtCore.Signal(int) - def __init__(self, repo: AddonManagerRepo, parent: QtCore.QObject = None): + def __init__(self, repo: Addon, parent: QtCore.QObject = None): super().__init__(parent) self.repo = repo def do_work(self): # Borrow the function from another class: checker = UpdateChecker() - if self.repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + if self.repo.repo_type == Addon.Kind.WORKBENCH: checker.check_workbench(self.repo) - elif self.repo.repo_type == AddonManagerRepo.RepoType.MACRO: + elif self.repo.repo_type == Addon.Kind.MACRO: checker.check_macro(self.repo) - elif self.repo.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif self.repo.repo_type == Addon.Kind.PACKAGE: checker.check_package(self.repo) self.update_status.emit(self.repo.update_status) @@ -388,10 +388,10 @@ class CheckSingleUpdateWorker(QtCore.QObject): class CheckWorkbenchesForUpdatesWorker(QtCore.QThread): """This worker checks for available updates for all workbenches""" - update_status = QtCore.Signal(AddonManagerRepo) + update_status = QtCore.Signal(Addon) progress_made = QtCore.Signal(int, int) - def __init__(self, repos: List[AddonManagerRepo]): + def __init__(self, repos: List[Addon]): QtCore.QThread.__init__(self) self.repos = repos @@ -408,14 +408,14 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread): return self.progress_made.emit(count, len(self.repos)) count += 1 - if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED: - if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + if repo.status() == Addon.Status.UNCHECKED: + if repo.repo_type == Addon.Kind.WORKBENCH: checker.check_workbench(repo) self.update_status.emit(repo) - elif repo.repo_type == AddonManagerRepo.RepoType.MACRO: + elif repo.repo_type == Addon.Kind.MACRO: checker.check_macro(repo) self.update_status.emit(repo) - elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif repo.repo_type == Addon.Kind.PACKAGE: checker.check_package(repo) self.update_status.emit(repo) @@ -427,7 +427,7 @@ class UpdateChecker: def check_workbench(self, wb): if not have_git or NOGIT: - wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK) + wb.set_status(Addon.Status.CANNOT_CHECK) return clonedir = self.moddir + os.sep + wb.name if os.path.exists(clonedir): @@ -441,7 +441,7 @@ class UpdateChecker: if gitrepo.head.is_detached: # By definition, in a detached-head state we cannot # update, so don't even bother checking. - wb.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE) + wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE) if hasattr(gitrepo.head, "ref"): wb.branch = gitrepo.head.ref.name else: @@ -458,17 +458,13 @@ class UpdateChecker: + "\n" ) FreeCAD.Console.PrintWarning(str(e) + "\n") - wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK) + wb.set_status(Addon.Status.CANNOT_CHECK) else: try: if "git pull" in gitrepo.git.status(): - wb.set_status( - AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE - ) + wb.set_status(Addon.Status.UPDATE_AVAILABLE) else: - wb.set_status( - AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE - ) + wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE) except Exception: FreeCAD.Console.PrintWarning( translate( @@ -476,16 +472,16 @@ class UpdateChecker: ).format(wb.name) + "\n" ) - wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK) + wb.set_status(Addon.Status.CANNOT_CHECK) - def check_package(self, package: AddonManagerRepo) -> None: + def check_package(self, package: Addon) -> None: clonedir = self.moddir + os.sep + package.name if os.path.exists(clonedir): # First, try to just do a git-based update, which will give the most accurate results: if have_git and not NOGIT: self.check_workbench(package) - if package.status() != AddonManagerRepo.UpdateStatus.CANNOT_CHECK: + if package.status() != Addon.Status.CANNOT_CHECK: # It worked, just exit now return @@ -495,7 +491,7 @@ class UpdateChecker: # If there is no package.xml file, then it's because the package author added it after the last time # the local installation was updated. By definition, then, there is an update available, if only to # download the new XML file. - package.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE) + package.set_status(Addon.Status.UPDATE_AVAILABLE) package.installed_version = None return else: @@ -506,11 +502,9 @@ class UpdateChecker: # Packages are considered up-to-date if the metadata version matches. Authors should update # their version string when they want the addon manager to alert users of a new version. if package.metadata.Version != installed_metadata.Version: - package.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE) + package.set_status(Addon.Status.UPDATE_AVAILABLE) else: - package.set_status( - AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE - ) + package.set_status(Addon.Status.NO_UPDATE_AVAILABLE) except Exception: FreeCAD.Console.PrintWarning( translate( @@ -519,9 +513,9 @@ class UpdateChecker: ).format(name=installed_metadata_file) + "\n" ) - package.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK) + package.set_status(Addon.Status.CANNOT_CHECK) - def check_macro(self, macro_wrapper: AddonManagerRepo) -> None: + def check_macro(self, macro_wrapper: Addon) -> None: # Make sure this macro has its code downloaded: try: if not macro_wrapper.macro.parsed and macro_wrapper.macro.on_git: @@ -542,7 +536,7 @@ class UpdateChecker: ).format(name=macro_wrapper.macro.name) + "\n" ) - macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK) + macro_wrapper.set_status(Addon.Status.CANNOT_CHECK) return hasher1 = hashlib.sha1() @@ -568,9 +562,9 @@ class UpdateChecker: else: return if new_sha1 == old_sha1: - macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE) + macro_wrapper.set_status(Addon.Status.NO_UPDATE_AVAILABLE) else: - macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE) + macro_wrapper.set_status(Addon.Status.UPDATE_AVAILABLE) class FillMacroListWorker(QtCore.QThread): @@ -669,7 +663,7 @@ class FillMacroListWorker(QtCore.QThread): macro = Macro(filename[:-8]) # Remove ".FCMacro". macro.on_git = True macro.src_filename = os.path.join(dirpath, filename) - repo = AddonManagerRepo.from_macro(macro) + repo = Addon.from_macro(macro) repo.url = "https://github.com/FreeCAD/FreeCAD-macros.git" utils.update_macro_installation_details(repo) self.add_macro_signal.emit(repo) @@ -713,7 +707,7 @@ class FillMacroListWorker(QtCore.QThread): macro_names.append(macname) macro = Macro(macname) macro.on_wiki = True - repo = AddonManagerRepo.from_macro(macro) + repo = Addon.from_macro(macro) repo.url = "https://wiki.freecad.org/Macros_recipes" utils.update_macro_installation_details(repo) self.add_macro_signal.emit(repo) @@ -723,10 +717,10 @@ class CacheMacroCode(QtCore.QThread): """Download and cache the macro code, and parse its internal metadata""" status_message = QtCore.Signal(str) - update_macro = QtCore.Signal(AddonManagerRepo) + update_macro = QtCore.Signal(Addon) progress_made = QtCore.Signal(int, int) - def __init__(self, repos: List[AddonManagerRepo]) -> None: + def __init__(self, repos: List[Addon]) -> None: QtCore.QThread.__init__(self) self.repos = repos self.workers = [] @@ -797,7 +791,7 @@ class CacheMacroCode(QtCore.QThread): ).format(num_macros=num_macros, num_failed=num_failed) ) - def update_and_advance(self, repo: AddonManagerRepo) -> None: + def update_and_advance(self, repo: Addon) -> None: if repo is not None: if repo.macro.name not in self.failed: self.update_macro.emit(repo) @@ -906,10 +900,10 @@ class InstallWorkbenchWorker(QtCore.QThread): status_message = QtCore.Signal(str) progress_made = QtCore.Signal(int, int) - success = QtCore.Signal(AddonManagerRepo, str) - failure = QtCore.Signal(AddonManagerRepo, str) + success = QtCore.Signal(Addon, str) + failure = QtCore.Signal(Addon, str) - def __init__(self, repo: AddonManagerRepo): + def __init__(self, repo: Addon): QtCore.QThread.__init__(self) self.repo = repo @@ -1094,7 +1088,7 @@ class InstallWorkbenchWorker(QtCore.QThread): "Addon successfully installed.", ) - if self.repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + if self.repo.repo_type == Addon.Kind.WORKBENCH: # symlink any macro contained in the module to the macros folder macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.exists(macro_dir): @@ -1375,7 +1369,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): status_message = QtCore.Signal(str) progress_made = QtCore.Signal(int, int) - package_updated = QtCore.Signal(AddonManagerRepo) + package_updated = QtCore.Signal(Addon) class RequestType(Enum): PACKAGE_XML = auto() @@ -1387,9 +1381,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): QtCore.QThread.__init__(self) self.repos = repos - self.requests: Dict[ - int, (AddonManagerRepo, UpdateMetadataCacheWorker.RequestType) - ] = {} + self.requests: Dict[int, (Addon, UpdateMetadataCacheWorker.RequestType)] = {} NetworkManager.AM_NETWORK_MANAGER.completed.connect(self.download_completed) self.requests_completed = 0 self.total_requests = 0 @@ -1470,8 +1462,8 @@ class UpdateMetadataCacheWorker(QtCore.QThread): elif request[1] == UpdateMetadataCacheWorker.RequestType.ICON: self.process_icon(request[0], data) - def process_package_xml(self, repo: AddonManagerRepo, data: QtCore.QByteArray): - repo.repo_type = AddonManagerRepo.RepoType.PACKAGE # By definition + def process_package_xml(self, repo: Addon, data: QtCore.QByteArray): + repo.repo_type = Addon.Kind.PACKAGE # By definition package_cache_directory = os.path.join(self.store, repo.name) if not os.path.exists(package_cache_directory): os.makedirs(package_cache_directory) @@ -1509,7 +1501,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): self.requests[index] = (repo, UpdateMetadataCacheWorker.RequestType.ICON) self.total_requests += 1 - def process_metadata_txt(self, repo: AddonManagerRepo, data: QtCore.QByteArray): + def process_metadata_txt(self, repo: Addon, data: QtCore.QByteArray): self.status_message.emit( translate("AddonsInstaller", "Downloaded metadata.txt for {}").format( repo.display_name @@ -1557,7 +1549,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): with open(new_xml_file, "wb") as f: f.write(data.data()) - def process_requirements_txt(self, repo: AddonManagerRepo, data: QtCore.QByteArray): + def process_requirements_txt(self, repo: Addon, data: QtCore.QByteArray): self.status_message.emit( translate( "AddonsInstaller", @@ -1583,7 +1575,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): with open(new_xml_file, "wb") as f: f.write(data.data()) - def process_icon(self, repo: AddonManagerRepo, data: QtCore.QByteArray): + def process_icon(self, repo: Addon, data: QtCore.QByteArray): self.status_message.emit( translate("AddonsInstaller", "Downloaded icon for {}").format( repo.display_name @@ -1625,8 +1617,8 @@ class UpdateAllWorker(QtCore.QThread): progress_made = QtCore.Signal(int, int) status_message = QtCore.Signal(str) - success = QtCore.Signal(AddonManagerRepo) - failure = QtCore.Signal(AddonManagerRepo) + success = QtCore.Signal(Addon) + failure = QtCore.Signal(Addon) def __init__(self, repos): super().__init__() @@ -1664,13 +1656,13 @@ class UpdateAllWorker(QtCore.QThread): for worker in workers: worker.wait() - def on_success(self, repo: AddonManagerRepo) -> None: + def on_success(self, repo: Addon) -> None: self.progress_made.emit( len(self.repos) - self.repo_queue.qsize(), len(self.repos) ) self.success.emit(repo) - def on_failure(self, repo: AddonManagerRepo) -> None: + def on_failure(self, repo: Addon) -> None: self.progress_made.emit( len(self.repos) - self.repo_queue.qsize(), len(self.repos) ) @@ -1678,8 +1670,8 @@ class UpdateAllWorker(QtCore.QThread): class UpdateSingleWorker(QtCore.QThread): - success = QtCore.Signal(AddonManagerRepo) - failure = QtCore.Signal(AddonManagerRepo) + success = QtCore.Signal(Addon) + failure = QtCore.Signal(Addon) def __init__(self, repo_queue: queue.Queue): super().__init__() @@ -1694,13 +1686,13 @@ class UpdateSingleWorker(QtCore.QThread): repo = self.repo_queue.get_nowait() except queue.Empty: return - if repo.repo_type == AddonManagerRepo.RepoType.MACRO: + if repo.repo_type == Addon.Kind.MACRO: self.update_macro(repo) else: self.update_package(repo) self.repo_queue.task_done() - def update_macro(self, repo: AddonManagerRepo): + def update_macro(self, repo: Addon): """Updating a macro happens in this function, in the current thread""" cache_path = os.path.join( @@ -1718,7 +1710,7 @@ class UpdateSingleWorker(QtCore.QThread): else: self.failure.emit(repo) - def update_package(self, repo: AddonManagerRepo): + def update_package(self, repo: Addon): """Updating a package re-uses the package installation worker, so actually spawns another thread that we block on""" worker = InstallWorkbenchWorker(repo) diff --git a/src/Mod/AddonManager/change_branch.py b/src/Mod/AddonManager/change_branch.py index a5143bc674..3480ba5d96 100644 --- a/src/Mod/AddonManager/change_branch.py +++ b/src/Mod/AddonManager/change_branch.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * -# * Copyright (c) 2022 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This library is free software; you can redistribute it and/or * # * modify it under the terms of the GNU Lesser General Public * diff --git a/src/Mod/AddonManager/install_to_toolbar.py b/src/Mod/AddonManager/install_to_toolbar.py index 6b0e6a9939..12cd1152fc 100644 --- a/src/Mod/AddonManager/install_to_toolbar.py +++ b/src/Mod/AddonManager/install_to_toolbar.py @@ -22,14 +22,14 @@ import FreeCAD import FreeCADGui from PySide2 import QtCore, QtWidgets -import AddonManagerRepo +import Addon import os translate = FreeCAD.Qt.translate -def ask_to_install_toolbar_button(repo: AddonManagerRepo) -> None: +def ask_to_install_toolbar_button(repo: Addon) -> None: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") do_not_show_dialog = pref.GetBool("dontShowAddMacroButtonDialog", False) if not do_not_show_dialog: @@ -46,7 +46,7 @@ def ask_to_install_toolbar_button(repo: AddonManagerRepo) -> None: def ask_for_toolbar( - repo: AddonManagerRepo, custom_toolbars + repo: Addon, custom_toolbars ) -> object: # Returns the pref group for the new toolbar pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") @@ -158,7 +158,7 @@ def check_for_toolbar(custom_toolbars, toolbar_name: str) -> bool: return False -def install_toolbar_button(repo: AddonManagerRepo) -> None: +def install_toolbar_button(repo: Addon) -> None: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") custom_toolbar_name = pref.GetString( "CustomToolbarName", "Auto-Created Macro Toolbar" @@ -206,7 +206,7 @@ def install_toolbar_button(repo: AddonManagerRepo) -> None: ) -def install_macro_to_toolbar(repo: AddonManagerRepo, toolbar: object) -> None: +def install_macro_to_toolbar(repo: Addon, toolbar: object) -> None: menuText = repo.display_name tooltipText = f"{repo.display_name}" if repo.macro.comment: @@ -254,7 +254,7 @@ def install_macro_to_toolbar(repo: AddonManagerRepo, toolbar: object) -> None: wb.reloadActive() -def remove_custom_toolbar_button(repo: AddonManagerRepo) -> None: +def remove_custom_toolbar_button(repo: Addon) -> None: """If this repo contains a macro, look through the custom commands and see if one is set up for this macro. If so, remove it, including any toolbar entries.""" diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index fab2ea8e59..79b3bf6204 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * -# * Copyright (c) 2021 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -34,7 +34,7 @@ import FreeCADGui import addonmanager_utilities as utils from addonmanager_workers import GetMacroDetailsWorker, CheckSingleUpdateWorker -from AddonManagerRepo import AddonManagerRepo +from Addon import Addon import NetworkManager from change_branch import ChangeBranchDialog @@ -71,12 +71,12 @@ except Exception: class PackageDetails(QWidget): back = Signal() - install = Signal(AddonManagerRepo) - uninstall = Signal(AddonManagerRepo) - update = Signal(AddonManagerRepo) - execute = Signal(AddonManagerRepo) - update_status = Signal(AddonManagerRepo) - check_for_update = Signal(AddonManagerRepo) + install = Signal(Addon) + uninstall = Signal(Addon) + update = Signal(Addon) + execute = Signal(Addon) + update_status = Signal(Addon) + check_for_update = Signal(Addon) def __init__(self, parent=None): super().__init__(parent) @@ -110,7 +110,7 @@ class PackageDetails(QWidget): self.ui.loadingLabel.show() self.ui.webView.hide() - def show_repo(self, repo: AddonManagerRepo, reload: bool = False) -> None: + def show_repo(self, repo: Addon, reload: bool = False) -> None: # If this is the same repo we were already showing, we do not have to do the # expensive refetch unless reload is true @@ -136,17 +136,17 @@ class PackageDetails(QWidget): self.worker.requestInterruption() self.worker.wait() - if repo.repo_type == AddonManagerRepo.RepoType.MACRO: + if repo.repo_type == Addon.Kind.MACRO: self.show_macro(repo) self.ui.buttonExecute.show() - elif repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: + elif repo.repo_type == Addon.Kind.WORKBENCH: self.show_workbench(repo) self.ui.buttonExecute.hide() - elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE: + elif repo.repo_type == Addon.Kind.PACKAGE: self.show_package(repo) self.ui.buttonExecute.hide() - if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED: + if repo.status() == Addon.Status.UNCHECKED: if not self.status_update_thread: self.status_update_thread = QThread() self.status_update_worker = CheckSingleUpdateWorker(repo) @@ -165,7 +165,7 @@ class PackageDetails(QWidget): repo = self.repo self.set_change_branch_button_state() self.set_disable_button_state() - if status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if status != Addon.Status.NOT_INSTALLED: version = repo.installed_version date = "" @@ -196,7 +196,7 @@ class PackageDetails(QWidget): translate("AddonsInstaller", "Installed") + ". " ) - if status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + if status == Addon.Status.UPDATE_AVAILABLE: if repo.metadata: installed_version_string += ( "" @@ -225,10 +225,10 @@ class PackageDetails(QWidget): ) + "." ) - elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: + elif status == Addon.Status.NO_UPDATE_AVAILABLE: detached_head = False branch = repo.branch - if have_git and repo.repo_type != AddonManagerRepo.RepoType.MACRO: + if have_git and repo.repo_type != Addon.Kind.MACRO: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) if os.path.exists(os.path.join(moddir, ".git")): @@ -252,14 +252,14 @@ class PackageDetails(QWidget): ).format(branch) + "." ) - elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: + elif status == Addon.Status.PENDING_RESTART: installed_version_string += ( translate( "AddonsInstaller", "Updated, please restart FreeCAD to use" ) + "." ) - elif status == AddonManagerRepo.UpdateStatus.UNCHECKED: + elif status == Addon.Status.UNCHECKED: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") autocheck = pref.GetBool("AutoCheck", False) @@ -275,7 +275,7 @@ class PackageDetails(QWidget): installed_version_string += "" self.ui.labelPackageDetails.setText(installed_version_string) - if repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + if repo.status() == Addon.Status.UPDATE_AVAILABLE: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.attention_color_string() ) @@ -302,32 +302,32 @@ class PackageDetails(QWidget): self.ui.labelPackageDetails.hide() self.ui.labelInstallationLocation.hide() - if status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if status == Addon.Status.NOT_INSTALLED: self.ui.buttonInstall.show() self.ui.buttonUninstall.hide() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() - elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: + elif status == Addon.Status.NO_UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() - elif status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + elif status == Addon.Status.UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() self.ui.buttonCheckForUpdate.hide() - elif status == AddonManagerRepo.UpdateStatus.UNCHECKED: + elif status == Addon.Status.UNCHECKED: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.show() - elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: + elif status == Addon.Status.PENDING_RESTART: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() - elif status == AddonManagerRepo.UpdateStatus.CANNOT_CHECK: + elif status == Addon.Status.CANNOT_CHECK: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() @@ -369,7 +369,10 @@ class PackageDetails(QWidget): self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" - + translate("AddonsInstaller", "WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.") + + translate( + "AddonsInstaller", + "WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.", + ) + "

" ) self.ui.labelWarningInfo.setStyleSheet( @@ -381,10 +384,7 @@ class PackageDetails(QWidget): def requires_newer_freecad(self) -> Optional[str]: # If it's not installed, check to see if it's for a newer version of FreeCAD - if ( - self.repo.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED - and self.repo.metadata - ): + if self.repo.status() == Addon.Status.NOT_INSTALLED and self.repo.metadata: # Only hide if ALL content items require a newer version, otherwise # it's possible that this package actually provides versions of itself # for newer and older versions @@ -416,11 +416,11 @@ class PackageDetails(QWidget): return # Is this repo installed? If not, return. - if self.repo.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if self.repo.status() == Addon.Status.NOT_INSTALLED: return # Is it a Macro? If so, return: - if self.repo.repo_type == AddonManagerRepo.RepoType.MACRO: + if self.repo.repo_type == Addon.Kind.MACRO: return # Can we actually switch branches? If not, return. @@ -441,14 +441,14 @@ class PackageDetails(QWidget): self.ui.buttonEnable.hide() self.ui.buttonDisable.hide() status = self.repo.status() - if status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if status != Addon.Status.NOT_INSTALLED: disabled = self.repo.is_disabled() if disabled: self.ui.buttonEnable.show() else: self.ui.buttonDisable.show() - def show_workbench(self, repo: AddonManagerRepo) -> None: + def show_workbench(self, repo: Addon) -> None: """loads information of a given workbench""" url = utils.get_readme_html_url(repo) if HAS_QTWEBENGINE: @@ -459,7 +459,7 @@ class PackageDetails(QWidget): text = readme_data.data().decode("utf8") self.ui.textBrowserReadMe.setHtml(text) - def show_package(self, repo: AddonManagerRepo) -> None: + def show_package(self, repo: Addon) -> None: """Show the details for a package (a repo with a package.xml metadata file)""" readme_url = None @@ -479,7 +479,7 @@ class PackageDetails(QWidget): text = readme_data.data().decode("utf8") self.ui.textBrowserReadMe.setHtml(text) - def show_macro(self, repo: AddonManagerRepo) -> None: + def show_macro(self, repo: Addon) -> None: """loads information of a given macro""" if not repo.macro.url: @@ -614,12 +614,13 @@ class PackageDetails(QWidget): self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" - + translate("AddonsInstaller", "This Addon will be enabled next time you restart FreeCAD.") + + translate( + "AddonsInstaller", + "This Addon will be enabled next time you restart FreeCAD.", + ) + "

" ) - self.ui.labelWarningInfo.setStyleSheet( - "color:" + utils.bright_color_string() - ) + self.ui.labelWarningInfo.setStyleSheet("color:" + utils.bright_color_string()) def disable_clicked(self) -> None: self.repo.disable() @@ -628,7 +629,10 @@ class PackageDetails(QWidget): self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" - + translate("AddonsInstaller", "This Addon will be disabled next time you restart FreeCAD.") + + translate( + "AddonsInstaller", + "This Addon will be disabled next time you restart FreeCAD.", + ) + "

" ) self.ui.labelWarningInfo.setStyleSheet( @@ -651,12 +655,12 @@ class PackageDetails(QWidget): self.repo.load_metadata_file(path_to_metadata) self.repo.installed_version = self.repo.metadata.Version else: - self.repo.repo_type = AddonManagerRepo.RepoType.WORKBENCH + self.repo.repo_type = Addon.Kind.WORKBENCH self.repo.metadata = None self.repo.installed_version = None self.repo.updated_timestamp = QDateTime.currentDateTime().toSecsSinceEpoch() self.repo.branch = name - self.repo.set_status(AddonManagerRepo.UpdateStatus.PENDING_RESTART) + self.repo.set_status(Addon.Status.PENDING_RESTART) installed_version_string = "

" installed_version_string += translate( diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 82ed989a8c..9ffd66ce76 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * -# * Copyright (c) 2021 Chris Hennes * +# * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -31,7 +31,7 @@ from PySide2.QtWidgets import * from enum import IntEnum import threading -from AddonManagerRepo import AddonManagerRepo +from Addon import Addon from compact_view import Ui_CompactView from expanded_view import Ui_ExpandedView @@ -56,7 +56,7 @@ class StatusFilter(IntEnum): class PackageList(QWidget): """A widget that shows a list of packages and various widgets to control the display of the list""" - itemSelected = Signal(AddonManagerRepo) + itemSelected = Signal(Addon) def __init__(self, parent=None): super().__init__(parent) @@ -206,15 +206,15 @@ class PackageListItemModel(QAbstractListModel): row = index.row() if role == Qt.ToolTipRole: tooltip = "" - if self.repos[row].repo_type == AddonManagerRepo.RepoType.PACKAGE: + if self.repos[row].repo_type == Addon.Kind.PACKAGE: tooltip = translate( "AddonsInstaller", "Click for details about package {}" ).format(self.repos[row].display_name) - elif self.repos[row].repo_type == AddonManagerRepo.RepoType.WORKBENCH: + elif self.repos[row].repo_type == Addon.Kind.WORKBENCH: tooltip = translate( "AddonsInstaller", "Click for details about workbench {}" ).format(self.repos[row].display_name) - elif self.repos[row].repo_type == AddonManagerRepo.RepoType.MACRO: + elif self.repos[row].repo_type == Addon.Kind.MACRO: tooltip = translate( "AddonsInstaller", "Click for details about macro {}" ).format(self.repos[row].display_name) @@ -246,7 +246,7 @@ class PackageListItemModel(QAbstractListModel): ) self.write_lock.release() - def append_item(self, repo: AddonManagerRepo) -> None: + def append_item(self, repo: Addon) -> None: if repo in self.repos: # Cowardly refuse to insert the same repo a second time return @@ -264,9 +264,7 @@ class PackageListItemModel(QAbstractListModel): self.endRemoveRows() self.write_lock.release() - def update_item_status( - self, name: str, status: AddonManagerRepo.UpdateStatus - ) -> None: + def update_item_status(self, name: str, status: Addon.Status) -> None: for row, item in enumerate(self.repos): if item.name == name: self.setData( @@ -282,7 +280,7 @@ class PackageListItemModel(QAbstractListModel): ) return - def reload_item(self, repo: AddonManagerRepo) -> None: + def reload_item(self, repo: Addon) -> None: for index, item in enumerate(self.repos): if item.name == repo.name: self.write_lock.acquire() @@ -414,32 +412,38 @@ class PackageListItemDelegate(QStyledItemDelegate): self.widget.adjustSize() - def get_compact_update_string(self, repo: AddonManagerRepo) -> str: + def get_compact_update_string(self, repo: Addon) -> str: """Get a single-line string listing details about the installed version and date""" result = "" - if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED: + if repo.status() == Addon.Status.UNCHECKED: result = translate("AddonsInstaller", "Installed") - elif repo.status() == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: + elif repo.status() == Addon.Status.NO_UPDATE_AVAILABLE: result = translate("AddonsInstaller", "Up-to-date") - elif repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + elif repo.status() == Addon.Status.UPDATE_AVAILABLE: result = translate("AddonsInstaller", "Update available") - elif repo.status() == AddonManagerRepo.UpdateStatus.PENDING_RESTART: + elif repo.status() == Addon.Status.PENDING_RESTART: result = translate("AddonsInstaller", "Pending restart") - + if repo.is_disabled(): - style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'" - result += f" [" + translate("AddonsInstaller","DISABLED") + "]" + style = ( + "style='color:" + utils.warning_color_string() + "; font-weight:bold;'" + ) + result += ( + f" [" + + translate("AddonsInstaller", "DISABLED") + + "]" + ) return result - def get_expanded_update_string(self, repo: AddonManagerRepo) -> str: + def get_expanded_update_string(self, repo: Addon) -> str: """Get a multi-line string listing details about the installed version and date""" result = "" installed_version_string = "" - if repo.status() != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if repo.status() != Addon.Status.NOT_INSTALLED: if repo.installed_version: installed_version_string = ( "
" + translate("AddonsInstaller", "Installed version") + ": " @@ -468,25 +472,31 @@ class PackageListItemDelegate(QStyledItemDelegate): ) available_version_string += repo.metadata.Version - if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED: + if repo.status() == Addon.Status.UNCHECKED: result = translate("AddonsInstaller", "Installed") result += installed_version_string result += installed_date_string - elif repo.status() == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: + elif repo.status() == Addon.Status.NO_UPDATE_AVAILABLE: result = translate("AddonsInstaller", "Up-to-date") result += installed_version_string result += installed_date_string - elif repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + elif repo.status() == Addon.Status.UPDATE_AVAILABLE: result = translate("AddonsInstaller", "Update available") result += installed_version_string result += installed_date_string result += available_version_string - elif repo.status() == AddonManagerRepo.UpdateStatus.PENDING_RESTART: + elif repo.status() == Addon.Status.PENDING_RESTART: result = translate("AddonsInstaller", "Pending restart") - + if repo.is_disabled(): - style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'" - result += f"
[" + translate("AddonsInstaller","DISABLED") + "]" + style = ( + "style='color:" + utils.warning_color_string() + "; font-weight:bold;'" + ) + result += ( + f"
[" + + translate("AddonsInstaller", "DISABLED") + + "]" + ) return result @@ -554,18 +564,18 @@ class PackageListFilter(QSortFilterProxyModel): return False if self.status == StatusFilter.INSTALLED: - if data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if data.status() == Addon.Status.NOT_INSTALLED: return False elif self.status == StatusFilter.NOT_INSTALLED: - if data.status() != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: + if data.status() != Addon.Status.NOT_INSTALLED: return False elif self.status == StatusFilter.UPDATE_AVAILABLE: - if data.status() != AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: + if data.status() != Addon.Status.UPDATE_AVAILABLE: return False # If it's not installed, check to see if it's Py2 only if ( - data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED + data.status() == Addon.Status.NOT_INSTALLED and self.hide_py2 and data.python2 ): @@ -573,7 +583,7 @@ class PackageListFilter(QSortFilterProxyModel): # If it's not installed, check to see if it's marked obsolete if ( - data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED + data.status() == Addon.Status.NOT_INSTALLED and self.hide_obsolete and data.obsolete ): @@ -581,7 +591,7 @@ class PackageListFilter(QSortFilterProxyModel): # If it's not installed, check to see if it's for a newer version of FreeCAD if ( - data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED + data.status() == Addon.Status.NOT_INSTALLED and self.hide_newer_freecad_required and data.metadata ):