diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index 9af58937dd..68166312b8 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -223,12 +223,16 @@ class Addon: @property def license(self): if not self._cached_license: + self._cached_license = "UNLICENSED" if self.metadata and self.metadata.license: self._cached_license = self.metadata.license elif self.stats and self.stats.license: self._cached_license = self.stats.license - elif self.macro and self.macro.license: - self._cached_license = self.macro.license + elif self.macro: + if self.macro.license: + self._cached_license = self.macro.license + elif self.macro.on_wiki: + self._cached_license = "CC-BY-3.0" return self._cached_license @classmethod diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 74333a3c51..ee73ccc8de 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -550,8 +550,9 @@ class CommandAddonManager: ) self.startup() - # Recaching implies checking for updates, regardless of the user's autocheck option - self.startup_sequence.remove(self.check_updates) + # Re-caching implies checking for updates, regardless of the user's autocheck option + if self.check_updates in self.startup_sequence: + self.startup_sequence.remove(self.check_updates) self.startup_sequence.append(self.force_check_updates) def on_package_updated(self, repo: Addon) -> None: diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 98a1be16be..acd3997360 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -391,7 +391,8 @@ if HAVE_QTNETWORK: def __synchronous_process_completion( self, index: int, code: int, data: QtCore.QByteArray ) -> None: - """Check the return status of a completed process, and handle its returned data (if any).""" + """Check the return status of a completed process, and handle its returned data (if + any).""" with self.synchronous_lock: if index in self.synchronous_complete: if code == 200: @@ -406,7 +407,8 @@ if HAVE_QTNETWORK: ) self.synchronous_complete[index] = True - def __create_get_request(self, url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest: + @staticmethod + def __create_get_request(url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest: """Construct a network request to a given URL""" request = QtNetwork.QNetworkRequest(QtCore.QUrl(url)) request.setAttribute( @@ -560,21 +562,20 @@ if HAVE_QTNETWORK: # This can happen during a cancellation operation: silently do nothing return - if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: - # Silently do nothing - return - index = None 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()}") return response_code = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute) - self.queue.task_done() + if response_code == 301: # Permanently moved -- this is a redirect, bail out + return + if reply.error() != QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: + # It this was not a timeout, make sure we mark the queue task done + self.queue.task_done() if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: if index in self.monitored_connections: # Make sure to read any remaining data diff --git a/src/Mod/AddonManager/addonmanager_licenses.py b/src/Mod/AddonManager/addonmanager_licenses.py index 1dcc40b87a..5781c093a2 100644 --- a/src/Mod/AddonManager/addonmanager_licenses.py +++ b/src/Mod/AddonManager/addonmanager_licenses.py @@ -75,6 +75,8 @@ class SPDXLicenseManager: def is_osi_approved(self, spdx_id: str) -> bool: """Check to see if the license is OSI-approved, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isOsiApproved".""" + if spdx_id == "UNLICENSED" or spdx_id == "UNLICENCED" or spdx_id.startswith("SEE LIC"): + return False if spdx_id not in self.license_data: fci.Console.PrintWarning( f"WARNING: License ID {spdx_id} is not in the SPDX license " @@ -89,6 +91,8 @@ class SPDXLicenseManager: def is_fsf_libre(self, spdx_id: str) -> bool: """Check to see if the license is FSF Free/Libre, according to the SPDX database. Returns False if the license is not in the database, or is not marked as "isFsfLibre".""" + if spdx_id == "UNLICENSED" or spdx_id == "UNLICENCED" or spdx_id.startswith("SEE LIC"): + return False if spdx_id not in self.license_data: fci.Console.PrintWarning( f"WARNING: License ID {spdx_id} is not in the SPDX license " @@ -100,6 +104,10 @@ class SPDXLicenseManager: ) def name(self, spdx_id: str) -> str: + if spdx_id == "UNLICENSED": + return "All rights reserved" + if spdx_id.startswith("SEE LIC"): # "SEE LICENSE IN" or "SEE LICENCE IN" + return f"Custom license: {spdx_id}" if spdx_id not in self.license_data: return "" return self.license_data[spdx_id]["name"] @@ -145,21 +153,24 @@ class SPDXLicenseManager: .replace("GPL2", "GPL-2") .replace("GPL3", "GPL-3") ) - if self.name(normed): + or_later = "" + if normed.endswith("+"): + normed = normed[:-1] + or_later = "-or-later" + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed + return normed + or_later # If it still doesn't match, try some other things while "--" in normed: - normed = license_string.replace("--", "-") + normed = normed.replace("--", "-") - if self.name(normed): + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed - if not normed.endswith(".0"): - normed += ".0" - if self.name(normed): + return normed + or_later + normed += ".0" + if self.name(normed + or_later): fci.Console.PrintLog(f"found valid SPDX license ID {normed}\n") - return normed + return normed + or_later fci.Console.PrintLog(f"failed to normalize (typo in ID or invalid version number??)\n") return license_string # We failed to normalize this one diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index 06b981583d..93e59f6b0e 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -160,9 +160,6 @@ class Macro: code = self._fetch_raw_code(p) if not code: code = self._read_code_from_wiki(p) - if not self.license: - # The default license on the wiki is CC-BY-3.0 (which is non-Libre and not OSI-approved) - self.license = "CC-BY-3.0" if not code: self._console.PrintWarning( translate("AddonsInstaller", "Unable to fetch the code of this macro.") + "\n" diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 41d6a81229..8b3f2582f6 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -380,7 +380,7 @@ def blocking_get(url: str, method=None) -> bytes: p = b"" if fci.FreeCADGui and method is None or method == "networkmanager": NetworkManager.InitializeNetworkManager() - p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url) + p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url, 10000) # 10 second timeout if p: try: p = p.data() diff --git a/src/Mod/AddonManager/addonmanager_workers_installation.py b/src/Mod/AddonManager/addonmanager_workers_installation.py index e1bcf1c226..1b2f902e53 100644 --- a/src/Mod/AddonManager/addonmanager_workers_installation.py +++ b/src/Mod/AddonManager/addonmanager_workers_installation.py @@ -87,7 +87,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): current_thread = QtCore.QThread.currentThread() for repo in self.repos: - if repo.url and utils.recognized_git_location(repo): + if not repo.macro and repo.url and utils.recognized_git_location(repo): # package.xml index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( utils.construct_git_url(repo, "package.xml") @@ -120,7 +120,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread): while self.requests: if current_thread.isInterruptionRequested(): - NetworkManager.AM_NETWORK_MANAGER.completed.disconnect(self.download_completed) for request in self.requests: NetworkManager.AM_NETWORK_MANAGER.abort(request) return diff --git a/src/Mod/AddonManager/addonmanager_workers_startup.py b/src/Mod/AddonManager/addonmanager_workers_startup.py index 0797d1f122..e08bf2f98c 100644 --- a/src/Mod/AddonManager/addonmanager_workers_startup.py +++ b/src/Mod/AddonManager/addonmanager_workers_startup.py @@ -93,7 +93,7 @@ class CreateAddonListWorker(QtCore.QThread): def _get_freecad_addon_repo_data(self): # update info lists p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json" + "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json", 5000 ) if p: p = p.data().decode("utf8") @@ -203,7 +203,7 @@ class CreateAddonListWorker(QtCore.QThread): def _get_official_addons(self): # querying official addons p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules" + "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules", 5000 ) if not p: return @@ -369,7 +369,7 @@ class CreateAddonListWorker(QtCore.QThread): """ p = NetworkManager.AM_NETWORK_MANAGER.blocking_get( - "https://wiki.freecad.org/Macros_recipes" + "https://wiki.freecad.org/Macros_recipes", 5000 ) if not p: # The Qt Python translation extractor doesn't support splitting this string (yet)