Addon Manager: Switch to QWebEngineView for README
Rather than manually parsing the output from the repo's host, the Addon Manager now uses an embedded QWebEngineView to display the README data. This makes the display more repo-location agnostic (rather than trying to parse the specific code from GitHub, Gitlab, etc.). Special handling of known hosts is still provided to improve the display of the page, but it is not required. Clicking a link on the page still loads in a new system browser window, with the exception of links to the FreeCAD wiki, which are loaded in the same browser. This is expected to be used primarily to access traslated pages for Macros, so no advanced web-browsing features are displayed (e.g. back buttons, history access, etc.).
This commit is contained in:
@@ -87,7 +87,6 @@ class CommandAddonManager:
|
||||
"update_metadata_cache_worker",
|
||||
"load_macro_metadata_worker",
|
||||
"update_all_worker",
|
||||
"update_check_single_worker",
|
||||
"dependency_installation_worker",
|
||||
]
|
||||
|
||||
@@ -370,9 +369,6 @@ class CommandAddonManager:
|
||||
# connect slots
|
||||
self.dialog.rejected.connect(self.reject)
|
||||
self.dialog.buttonUpdateAll.clicked.connect(self.update_all)
|
||||
self.dialog.buttonCheckForUpdates.clicked.connect(
|
||||
self.manually_check_for_updates
|
||||
)
|
||||
self.dialog.buttonClose.clicked.connect(self.dialog.reject)
|
||||
self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked)
|
||||
self.dialog.buttonPauseUpdate.clicked.connect(self.stop_update)
|
||||
@@ -384,7 +380,6 @@ class CommandAddonManager:
|
||||
self.packageDetails.update.connect(self.update)
|
||||
self.packageDetails.back.connect(self.on_buttonBack_clicked)
|
||||
self.packageDetails.update_status.connect(self.status_updated)
|
||||
self.packageDetails.check_for_update.connect(self.check_for_update)
|
||||
|
||||
# center the dialog over the FreeCAD window
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
@@ -759,7 +754,7 @@ class CommandAddonManager:
|
||||
|
||||
def status_updated(self, repo: AddonManagerRepo) -> None:
|
||||
self.item_model.reload_item(repo)
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
self.packages_with_updates.append(repo)
|
||||
self.enable_updates(len(self.packages_with_updates))
|
||||
|
||||
@@ -881,7 +876,7 @@ class CommandAddonManager:
|
||||
|
||||
missing_external_addons = []
|
||||
for dep in deps.required_external_addons:
|
||||
if dep.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if dep.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
missing_external_addons.append(dep)
|
||||
|
||||
# Now check the loaded addons to see if we are missing an internal workbench:
|
||||
@@ -1181,62 +1176,16 @@ class CommandAddonManager:
|
||||
def update(self, repo: AddonManagerRepo) -> None:
|
||||
self.install(repo)
|
||||
|
||||
def check_for_update(self, repo: AddonManagerRepo) -> None:
|
||||
"""Check a single repo for available updates asynchronously"""
|
||||
|
||||
if (
|
||||
hasattr(self, "update_check_single_worker")
|
||||
and self.update_check_single_worker
|
||||
):
|
||||
if self.update_check_single_worker.isRunning():
|
||||
self.update_check_single_worker.blockSignals(True)
|
||||
self.update_check_single_worker.requestInterrupt()
|
||||
self.update_check_single_worker.wait()
|
||||
|
||||
self.update_check_single_worker = CheckSingleWorker(repo.name)
|
||||
self.update_check_single_worker.updateAvailable.connect(
|
||||
lambda update_available: self.mark_repo_update_available(
|
||||
repo, update_available
|
||||
)
|
||||
)
|
||||
self.update_check_single_worker.start()
|
||||
|
||||
def mark_repo_update_available(
|
||||
self, repo: AddonManagerRepo, available: bool
|
||||
) -> None:
|
||||
if available:
|
||||
repo.update_status = AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
repo.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE)
|
||||
else:
|
||||
repo.update_status = AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
repo.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE)
|
||||
self.item_model.reload_item(repo)
|
||||
self.packageDetails.show_repo(repo)
|
||||
|
||||
def manually_check_for_updates(self) -> None:
|
||||
if hasattr(self, "check_worker"):
|
||||
thread = self.check_worker
|
||||
if thread:
|
||||
if not thread.isFinished():
|
||||
self.do_next_startup_phase()
|
||||
return
|
||||
self.dialog.buttonCheckForUpdates.setText(
|
||||
translate("AddonsInstaller", "Checking for updates...")
|
||||
)
|
||||
self.dialog.buttonCheckForUpdates.setEnabled(False)
|
||||
self.show_progress_widgets()
|
||||
self.current_progress_region = 1
|
||||
self.number_of_progress_regions = 1
|
||||
self.check_worker = CheckWorkbenchesForUpdatesWorker(self.item_model.repos)
|
||||
self.check_worker.finished.connect(self.manual_update_check_complete)
|
||||
self.check_worker.progress_made.connect(self.update_progress_bar)
|
||||
self.check_worker.update_status.connect(self.status_updated)
|
||||
self.check_worker.start()
|
||||
|
||||
def manual_update_check_complete(self) -> None:
|
||||
self.dialog.buttonUpdateAll.show()
|
||||
self.dialog.buttonCheckForUpdates.hide()
|
||||
self.enable_updates(len(self.packages_with_updates))
|
||||
self.hide_progress_widgets()
|
||||
|
||||
def update_all(self) -> None:
|
||||
"""Asynchronously apply all available updates: individual failures are noted, but do not stop other updates"""
|
||||
|
||||
@@ -1325,11 +1274,9 @@ class CommandAddonManager:
|
||||
for installed_repo in self.subupdates_succeeded:
|
||||
if not installed_repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
self.restart_required = True
|
||||
installed_repo.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.PENDING_RESTART
|
||||
)
|
||||
installed_repo.set_status(AddonManagerRepo.UpdateStatus.PENDING_RESTART)
|
||||
else:
|
||||
installed_repo.update_status = (
|
||||
installed_repo.set_status(
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
self.item_model.reload_item(installed_repo)
|
||||
@@ -1404,10 +1351,10 @@ class CommandAddonManager:
|
||||
QtWidgets.QMessageBox.Close,
|
||||
)
|
||||
if repo.repo_type != AddonManagerRepo.RepoType.MACRO:
|
||||
repo.update_status = AddonManagerRepo.UpdateStatus.PENDING_RESTART
|
||||
repo.set_status(AddonManagerRepo.UpdateStatus.PENDING_RESTART)
|
||||
self.restart_required = True
|
||||
else:
|
||||
repo.update_status = AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
repo.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE)
|
||||
self.item_model.reload_item(repo)
|
||||
self.packageDetails.show_repo(repo)
|
||||
|
||||
@@ -1504,13 +1451,19 @@ class CommandAddonManager:
|
||||
)
|
||||
|
||||
# Second, run the Addon's "uninstall.py" script, if it exists
|
||||
uninstall_script = os.path.join(clonedir,"uninstall.py")
|
||||
uninstall_script = os.path.join(clonedir, "uninstall.py")
|
||||
if os.path.exists(uninstall_script):
|
||||
try:
|
||||
with open(uninstall_script, 'r') as f:
|
||||
with open(uninstall_script, "r") as f:
|
||||
exec(f.read())
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintError(translate("AddonsInstaller","Execution of Addon's uninstall.py script failed. Proceeding with uninstall...") + "\n")
|
||||
FreeCAD.Console.PrintError(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Execution of Addon's uninstall.py script failed. Proceeding with uninstall...",
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if os.path.exists(clonedir):
|
||||
shutil.rmtree(clonedir, onerror=self.remove_readonly)
|
||||
|
||||
@@ -23,9 +23,12 @@
|
||||
import FreeCAD
|
||||
|
||||
import os
|
||||
from typing import Dict, Set, List
|
||||
from urllib.parse import urlparse
|
||||
from typing import Dict, Set
|
||||
from threading import Lock
|
||||
|
||||
from addonmanager_macro import Macro
|
||||
import addonmanager_utilities as utils
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
@@ -54,6 +57,7 @@ class AddonManagerRepo:
|
||||
NO_UPDATE_AVAILABLE = 2
|
||||
UPDATE_AVAILABLE = 3
|
||||
PENDING_RESTART = 4
|
||||
CANNOT_CHECK = 5 # If we don't have git, etc.
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
@@ -71,6 +75,8 @@ class AddonManagerRepo:
|
||||
return "Update available"
|
||||
elif self.value == 4:
|
||||
return "Restart required"
|
||||
elif self.value == 5:
|
||||
return "Can't check"
|
||||
|
||||
class Dependencies:
|
||||
def __init__(self):
|
||||
@@ -90,16 +96,32 @@ class AddonManagerRepo:
|
||||
self.display_name = self.name
|
||||
self.url = url.strip()
|
||||
self.branch = branch.strip()
|
||||
self.update_status = status
|
||||
self.python2 = False
|
||||
self.obsolete = False
|
||||
self.rejected = False
|
||||
self.repo_type = AddonManagerRepo.RepoType.WORKBENCH
|
||||
self.description = None
|
||||
self.tags = set() # Just a cache, loaded from Metadata
|
||||
|
||||
# To prevent multiple threads from running git actions on this repo at the same time
|
||||
self.git_lock = Lock()
|
||||
|
||||
# To prevent multiple threads from accessing the status at the same time
|
||||
self.status_lock = Lock()
|
||||
self.set_status(status)
|
||||
|
||||
from addonmanager_utilities import construct_git_url
|
||||
|
||||
if "github" in self.url or "gitlab" in self.url or "salsa" in self.url:
|
||||
# The url should never end in ".git", so strip it if it's there
|
||||
parsed_url = urlparse(self.url)
|
||||
if parsed_url.path.endswith(".git"):
|
||||
self.url = parsed_url.scheme + parsed_url.path[:-4]
|
||||
if parsed_url.query:
|
||||
self.url += "?" + parsed_url.query
|
||||
if parsed_url.fragment:
|
||||
self.url += "#" + parsed_url.fragment
|
||||
|
||||
if utils.recognized_git_location(self):
|
||||
self.metadata_url = construct_git_url(self, "package.xml")
|
||||
else:
|
||||
self.metadata_url = None
|
||||
@@ -334,3 +356,11 @@ class AddonManagerRepo:
|
||||
for dep in self.blocks:
|
||||
if dep in all_repos:
|
||||
deps.blockers[dep] = all_repos[dep]
|
||||
|
||||
def status(self):
|
||||
with self.status_lock:
|
||||
return self.update_status
|
||||
|
||||
def set_status(self, status):
|
||||
with self.status_lock:
|
||||
self.update_status = status
|
||||
|
||||
@@ -20,6 +20,7 @@ SET(AddonManager_SRCS
|
||||
NetworkManager.py
|
||||
package_list.py
|
||||
package_details.py
|
||||
loading.html
|
||||
)
|
||||
|
||||
SOURCE_GROUP("" FILES ${AddonManager_SRCS})
|
||||
|
||||
@@ -146,27 +146,24 @@ def get_zip_url(repo):
|
||||
|
||||
def recognized_git_location(repo) -> bool:
|
||||
parsed_url = urlparse(repo.url)
|
||||
if (
|
||||
parsed_url.netloc == "github.com"
|
||||
or parsed_url.netloc == "framagit.com"
|
||||
or parsed_url.netloc == "gitlab.com"
|
||||
or parsed_url.netloc == "salsa.debian.org"
|
||||
):
|
||||
if parsed_url.netloc in [
|
||||
"github.com",
|
||||
"framagit.org",
|
||||
"gitlab.com",
|
||||
"salsa.debian.org",
|
||||
]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def construct_git_url(repo, filename):
|
||||
"Returns a direct download link to a file in an online Git repo: works with github, gitlab, and framagit"
|
||||
"Returns a direct download link to a file in an online Git repo: works with github, gitlab, framagit, and salsa.debian.org"
|
||||
|
||||
parsed_url = urlparse(repo.url)
|
||||
if parsed_url.netloc == "github.com" or parsed_url.netloc == "framagit.com":
|
||||
if parsed_url.netloc == "github.com":
|
||||
return f"{repo.url}/raw/{repo.branch}/{filename}"
|
||||
elif parsed_url.netloc == "gitlab.com":
|
||||
return f"{repo.url}/-/raw/{repo.branch}/{filename}"
|
||||
elif parsed_url.netloc == "salsa.debian.org":
|
||||
# e.g. https://salsa.debian.org/joha2/pyrate/-/raw/master/package.xml
|
||||
elif parsed_url.netloc in ["gitlab.com", "framagit.org", "salsa.debian.org"]:
|
||||
return f"{repo.url}/-/raw/{repo.branch}/{filename}"
|
||||
else:
|
||||
FreeCAD.Console.PrintLog(
|
||||
@@ -216,39 +213,12 @@ def get_readme_html_url(repo):
|
||||
parsedUrl = urlparse(repo.url)
|
||||
if parsedUrl.netloc == "github.com":
|
||||
return f"{repo.url}/blob/{repo.branch}/README.md"
|
||||
elif parsedUrl.netloc in ["gitlab.com", "salsa.debian.org", "framagit.org"]:
|
||||
return f"{repo.url}/-/blob/{repo.branch}/README.md"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_readme_regex(repo):
|
||||
"""Return a regex string that extracts the contents to be displayed in the description
|
||||
panel of the Addon manager, from raw HTML data (the readme's html rendering usually)"""
|
||||
|
||||
parsedUrl = urlparse(repo.url)
|
||||
if parsedUrl.netloc == "github.com":
|
||||
return "<article.*?>(.*?)</article>"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def fix_relative_links(text, base_url):
|
||||
"""Replace markdown image relative links with
|
||||
absolute ones using the base URL"""
|
||||
|
||||
new_text = ""
|
||||
for line in text.splitlines():
|
||||
for link in re.findall(r"!\[.*?\]\((.*?)\)", line) + re.findall(
|
||||
r"src\s*=\s*[\"'](.+?)[\"']", line
|
||||
):
|
||||
parts = link.split("/")
|
||||
if len(parts) < 2 or not re.match(r"^http|^www|^.+\.|^/", parts[0]):
|
||||
newlink = os.path.join(base_url, link.lstrip("./"))
|
||||
line = line.replace(link, newlink)
|
||||
FreeCAD.Console.PrintLog("Debug: replaced " + link + " with " + newlink)
|
||||
new_text = new_text + "\n" + line
|
||||
return new_text
|
||||
|
||||
|
||||
def repair_git_repo(repo_url: str, clone_dir: str) -> None:
|
||||
# Repair addon installed with raw download by adding the .git
|
||||
# directory to it
|
||||
|
||||
@@ -359,6 +359,29 @@ class LoadMacrosFromCacheWorker(QtCore.QThread):
|
||||
self.add_macro_signal.emit(repo)
|
||||
|
||||
|
||||
class CheckSingleUpdateWorker(QtCore.QObject):
|
||||
"""This worker is a little different from the others: the actual recommended way of
|
||||
running in a QThread is to make a worker object that gets moved into the thread."""
|
||||
|
||||
update_status = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, repo: AddonManagerRepo, 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:
|
||||
checker.check_workbench(self.repo)
|
||||
elif self.repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
checker.check_macro(self.repo)
|
||||
elif self.repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
checker.check_package(self.repo)
|
||||
|
||||
self.update_status.emit(self.repo.update_status)
|
||||
|
||||
|
||||
class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
"""This worker checks for available updates for all workbenches"""
|
||||
|
||||
@@ -372,61 +395,73 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
|
||||
def run(self):
|
||||
|
||||
if NOGIT or not have_git:
|
||||
return
|
||||
self.current_thread = QtCore.QThread.currentThread()
|
||||
self.basedir = FreeCAD.getUserAppDataDir()
|
||||
self.moddir = self.basedir + os.sep + "Mod"
|
||||
checker = UpdateChecker()
|
||||
count = 1
|
||||
for repo in self.repos:
|
||||
if self.current_thread.isInterruptionRequested():
|
||||
return
|
||||
self.progress_made.emit(count, len(self.repos))
|
||||
count += 1
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH:
|
||||
self.check_workbench(repo)
|
||||
checker.check_workbench(repo)
|
||||
self.update_status.emit(repo)
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
self.check_macro(repo)
|
||||
checker.check_macro(repo)
|
||||
self.update_status.emit(repo)
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
self.check_package(repo)
|
||||
checker.check_package(repo)
|
||||
self.update_status.emit(repo)
|
||||
|
||||
|
||||
class UpdateChecker:
|
||||
def __init__(self):
|
||||
self.basedir = FreeCAD.getUserAppDataDir()
|
||||
self.moddir = self.basedir + os.sep + "Mod"
|
||||
|
||||
def check_workbench(self, wb):
|
||||
if not have_git or NOGIT:
|
||||
wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK)
|
||||
return
|
||||
clonedir = self.moddir + os.sep + wb.name
|
||||
if os.path.exists(clonedir):
|
||||
# mark as already installed AND already checked for updates
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
utils.repair_git_repo(wb.url, clonedir)
|
||||
gitrepo = git.Git(clonedir)
|
||||
try:
|
||||
gitrepo.fetch()
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"AddonManager: "
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"Unable to fetch git updates for workbench {}",
|
||||
).format(wb.name)
|
||||
)
|
||||
else:
|
||||
with wb.git_lock:
|
||||
utils.repair_git_repo(wb.url, clonedir)
|
||||
with wb.git_lock:
|
||||
gitrepo = git.Git(clonedir)
|
||||
try:
|
||||
if "git pull" in gitrepo.status():
|
||||
wb.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
)
|
||||
else:
|
||||
wb.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
self.update_status.emit(wb)
|
||||
gitrepo.fetch()
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate("AddonsInstaller", "git fetch failed for {}").format(
|
||||
wb.name
|
||||
)
|
||||
"AddonManager: "
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"Unable to fetch git updates for workbench {}",
|
||||
).format(wb.name)
|
||||
)
|
||||
wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK)
|
||||
else:
|
||||
try:
|
||||
if "git pull" in gitrepo.status():
|
||||
wb.set_status(
|
||||
AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
)
|
||||
else:
|
||||
wb.set_status(
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate(
|
||||
"AddonsInstaller", "git fetch failed for {}"
|
||||
).format(wb.name)
|
||||
)
|
||||
wb.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK)
|
||||
|
||||
def check_package(self, package: AddonManagerRepo) -> None:
|
||||
clonedir = self.moddir + os.sep + package.name
|
||||
@@ -436,9 +471,8 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
# 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.update_status = AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
package.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE)
|
||||
package.installed_version = None
|
||||
self.update_status.emit(package)
|
||||
return
|
||||
else:
|
||||
package.updated_timestamp = os.path.getmtime(installed_metadata_file)
|
||||
@@ -448,14 +482,11 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
# 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.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
)
|
||||
package.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE)
|
||||
else:
|
||||
package.update_status = (
|
||||
package.set_status(
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
self.update_status.emit(package)
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate(
|
||||
@@ -464,6 +495,7 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
).format(name=installed_metadata_file)
|
||||
+ "\n"
|
||||
)
|
||||
package.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK)
|
||||
|
||||
def check_macro(self, macro_wrapper: AddonManagerRepo) -> None:
|
||||
# Make sure this macro has its code downloaded:
|
||||
@@ -486,6 +518,7 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
).format(name=macro_wrapper.macro.name)
|
||||
+ "\n"
|
||||
)
|
||||
macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.CANNOT_CHECK)
|
||||
return
|
||||
|
||||
hasher1 = hashlib.sha1()
|
||||
@@ -511,12 +544,9 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
else:
|
||||
return
|
||||
if new_sha1 == old_sha1:
|
||||
macro_wrapper.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE)
|
||||
else:
|
||||
macro_wrapper.update_status = AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
self.update_status.emit(macro_wrapper)
|
||||
macro_wrapper.set_status(AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE)
|
||||
|
||||
|
||||
class FillMacroListWorker(QtCore.QThread):
|
||||
@@ -801,189 +831,6 @@ class CacheMacroCode(QtCore.QThread):
|
||||
self.failed.append(macro_name)
|
||||
|
||||
|
||||
class ShowWorker(QtCore.QThread):
|
||||
"""This worker retrieves info of a given workbench"""
|
||||
|
||||
status_message = QtCore.Signal(str)
|
||||
readme_updated = QtCore.Signal(str)
|
||||
update_status = QtCore.Signal(AddonManagerRepo)
|
||||
|
||||
def __init__(self, repo, cache_path):
|
||||
|
||||
QtCore.QThread.__init__(self)
|
||||
self.repo = repo
|
||||
self.cache_path = cache_path
|
||||
|
||||
def run(self):
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Retrieving description...")
|
||||
)
|
||||
u = None
|
||||
url = self.repo.url
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Retrieving info from {}").format(str(url))
|
||||
)
|
||||
desc = ""
|
||||
regex = utils.get_readme_regex(self.repo)
|
||||
if regex:
|
||||
# extract readme from html via regex
|
||||
readmeurl = utils.get_readme_html_url(self.repo)
|
||||
if not readmeurl:
|
||||
FreeCAD.Console.PrintLog(f"README not found for {url}\n")
|
||||
p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(readmeurl)
|
||||
if not p:
|
||||
FreeCAD.Console.PrintLog(f"Debug: README not found at {readmeurl}\n")
|
||||
p = p.data().decode("utf8")
|
||||
readme = re.findall(regex, p, flags=re.MULTILINE | re.DOTALL)
|
||||
if readme:
|
||||
desc = readme[0]
|
||||
else:
|
||||
FreeCAD.Console.PrintLog(f"Debug: README not found at {readmeurl}\n")
|
||||
else:
|
||||
# convert raw markdown using lib
|
||||
readmeurl = utils.get_readme_url(self.repo)
|
||||
if not readmeurl:
|
||||
FreeCAD.Console.PrintLog(f"Debug: README not found for {url}\n")
|
||||
p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(readmeurl)
|
||||
if p:
|
||||
p = p.data().decode("utf8")
|
||||
desc = utils.fix_relative_links(p, readmeurl.rsplit("/README.md")[0])
|
||||
if not NOMARKDOWN and have_markdown:
|
||||
desc = markdown.markdown(desc, extensions=["md_in_html"])
|
||||
else:
|
||||
message = """
|
||||
<div style="width: 100%; text-align:center;background: #91bbe0;">
|
||||
<strong style="color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "Raw markdown displayed")
|
||||
message += "</strong><br/><br/>"
|
||||
message += translate(
|
||||
"AddonsInstaller", "Python Markdown library is missing."
|
||||
)
|
||||
message += "<br/></div><hr/><pre>" + desc + "</pre>"
|
||||
desc = message
|
||||
else:
|
||||
FreeCAD.Console.PrintLog("Debug: README not found at {readmeurl}\n")
|
||||
if desc == "":
|
||||
# fall back to the description text
|
||||
p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url)
|
||||
if not p:
|
||||
return
|
||||
p = p.data().decode("utf8")
|
||||
descregex = utils.get_desc_regex(self.repo)
|
||||
if descregex:
|
||||
desc = re.findall(descregex, p)
|
||||
if desc:
|
||||
desc = desc[0]
|
||||
if not desc:
|
||||
desc = "Unable to retrieve addon description"
|
||||
self.repo.description = desc
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
message = desc
|
||||
if self.repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
# Addon is installed but we haven't checked it yet, so let's check if it has an update
|
||||
upd = False
|
||||
# checking for updates
|
||||
if not NOGIT and have_git:
|
||||
repo = self.repo
|
||||
clonedir = (
|
||||
FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + repo.name
|
||||
)
|
||||
if os.path.exists(clonedir):
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
utils.repair_git_repo(self.repo.url, clonedir)
|
||||
gitrepo = git.Git(clonedir)
|
||||
gitrepo.fetch()
|
||||
if "git pull" in gitrepo.status():
|
||||
upd = True
|
||||
if upd:
|
||||
self.repo.update_status = AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE
|
||||
else:
|
||||
self.repo.update_status = (
|
||||
AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
)
|
||||
self.update_status.emit(self.repo)
|
||||
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
self.readme_updated.emit(message)
|
||||
self.mustLoadImages = True
|
||||
label = self.loadImages(message, self.repo.url, self.repo.name)
|
||||
if label:
|
||||
self.readme_updated.emit(label)
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
|
||||
def stopImageLoading(self):
|
||||
"this stops the image loading process and allow the thread to terminate earlier"
|
||||
|
||||
self.mustLoadImages = False
|
||||
|
||||
def loadImages(self, message, url, wbName):
|
||||
"checks if the given page contains images and downloads them"
|
||||
|
||||
# QTextBrowser cannot display online images. So we download them
|
||||
# here, and replace the image link in the html code with the
|
||||
# downloaded version
|
||||
|
||||
imagepaths = re.findall('<img.*?src="(.*?)"', message)
|
||||
if imagepaths:
|
||||
store = os.path.join(self.cache_path, "Images")
|
||||
if not os.path.exists(store):
|
||||
os.makedirs(store)
|
||||
with open(os.path.join(store, "download_in_progress"), "w") as f:
|
||||
f.write(
|
||||
"If this file still exists, it's because a download was interrupted. It can be safely ignored."
|
||||
)
|
||||
for path in imagepaths:
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return message
|
||||
if not self.mustLoadImages:
|
||||
return message
|
||||
origpath = path
|
||||
if "?" in path:
|
||||
# remove everything after the ?
|
||||
path = path.split("?")[0]
|
||||
if not path.startswith("http"):
|
||||
path = utils.getserver(url) + path
|
||||
name = path.split("/")[-1]
|
||||
if name and path.startswith("http"):
|
||||
storename = os.path.join(store, name)
|
||||
if len(storename) >= 260:
|
||||
remainChars = 259 - (len(store) + len(wbName) + 1)
|
||||
storename = os.path.join(store, wbName + name[-remainChars:])
|
||||
if not os.path.exists(storename):
|
||||
try:
|
||||
imagedata = NetworkManager.AM_NETWORK_MANAGER.blocking_get(
|
||||
path
|
||||
)
|
||||
if not imagedata:
|
||||
raise Exception
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintLog(
|
||||
"AddonManager: Debug: Error retrieving image from "
|
||||
+ path
|
||||
)
|
||||
else:
|
||||
try:
|
||||
f = open(storename, "wb")
|
||||
except OSError:
|
||||
# ecryptfs (and probably not only ecryptfs) has
|
||||
# lower length limit for path
|
||||
storename = storename[-140:]
|
||||
f = open(storename, "wb")
|
||||
f.write(imagedata.data())
|
||||
f.close()
|
||||
message = message.replace(
|
||||
'src="' + origpath,
|
||||
'src="file:///' + storename.replace("\\", "/"),
|
||||
)
|
||||
os.remove(os.path.join(store, "download_in_progress"))
|
||||
return message
|
||||
return None
|
||||
|
||||
|
||||
class GetMacroDetailsWorker(QtCore.QThread):
|
||||
"""Retrieve the macro details for a macro"""
|
||||
|
||||
@@ -1126,33 +973,34 @@ class InstallWorkbenchWorker(QtCore.QThread):
|
||||
+ str(self.repo.name)
|
||||
+ "\n"
|
||||
)
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
utils.repair_git_repo(self.repo.url, clonedir)
|
||||
repo = git.Git(clonedir)
|
||||
try:
|
||||
repo.pull() # Refuses to take a progress object?
|
||||
answer = translate(
|
||||
"AddonsInstaller",
|
||||
"Workbench successfully updated. Please restart FreeCAD to apply the changes.",
|
||||
)
|
||||
except Exception as e:
|
||||
answer = (
|
||||
translate("AddonsInstaller", "Error updating module ")
|
||||
+ self.repo.name
|
||||
+ " - "
|
||||
+ translate("AddonsInstaller", "Please fix manually")
|
||||
+ " -- \n"
|
||||
)
|
||||
answer += str(e)
|
||||
self.failure.emit(self.repo, answer)
|
||||
else:
|
||||
# Update the submodules for this repository
|
||||
repo_sms = git.Repo(clonedir)
|
||||
self.status_message.emit("Updating submodules...")
|
||||
for submodule in repo_sms.submodules:
|
||||
submodule.update(init=True, recursive=True)
|
||||
self.update_metadata()
|
||||
self.success.emit(self.repo, answer)
|
||||
with self.repo.git_lock:
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
utils.repair_git_repo(self.repo.url, clonedir)
|
||||
repo = git.Git(clonedir)
|
||||
try:
|
||||
repo.pull() # Refuses to take a progress object?
|
||||
answer = translate(
|
||||
"AddonsInstaller",
|
||||
"Workbench successfully updated. Please restart FreeCAD to apply the changes.",
|
||||
)
|
||||
except Exception as e:
|
||||
answer = (
|
||||
translate("AddonsInstaller", "Error updating module ")
|
||||
+ self.repo.name
|
||||
+ " - "
|
||||
+ translate("AddonsInstaller", "Please fix manually")
|
||||
+ " -- \n"
|
||||
)
|
||||
answer += str(e)
|
||||
self.failure.emit(self.repo, answer)
|
||||
else:
|
||||
# Update the submodules for this repository
|
||||
repo_sms = git.Repo(clonedir)
|
||||
self.status_message.emit("Updating submodules...")
|
||||
for submodule in repo_sms.submodules:
|
||||
submodule.update(init=True, recursive=True)
|
||||
self.update_metadata()
|
||||
self.success.emit(self.repo, answer)
|
||||
|
||||
def run_git_clone(self, clonedir: str) -> None:
|
||||
self.status_message.emit("Checking module dependencies...")
|
||||
@@ -1168,21 +1016,39 @@ class InstallWorkbenchWorker(QtCore.QThread):
|
||||
self.status_message.emit("Cloning module...")
|
||||
current_thread = QtCore.QThread.currentThread()
|
||||
|
||||
# NOTE: There is no way to interrupt this process in GitPython: someday we should
|
||||
# support pygit2/libgit2 so we can actually interrupt this properly.
|
||||
repo = git.Repo.clone_from(self.repo.url, clonedir, progress=self.git_progress)
|
||||
if current_thread.isInterruptionRequested():
|
||||
return
|
||||
FreeCAD.Console.PrintMessage("Cloning repo...\n")
|
||||
if self.repo.git_lock.locked():
|
||||
FreeCAD.Console.PrintMessage("Waiting for lock to be released to us...\n")
|
||||
if not self.repo.git_lock.acquire(timeout=2):
|
||||
FreeCAD.Console.PrintError("Timeout waiting for a lock on the git process, failed to clone repo\n")
|
||||
return
|
||||
else:
|
||||
self.repo.git_lock.release()
|
||||
|
||||
# Make sure to clone all the submodules as well
|
||||
if repo.submodules:
|
||||
repo.submodule_update(recursive=True)
|
||||
with self.repo.git_lock:
|
||||
FreeCAD.Console.PrintMessage("Lock acquired...\n")
|
||||
# NOTE: There is no way to interrupt this process in GitPython: someday we should
|
||||
# support pygit2/libgit2 so we can actually interrupt this properly.
|
||||
repo = git.Repo.clone_from(
|
||||
self.repo.url, clonedir, progress=self.git_progress
|
||||
)
|
||||
FreeCAD.Console.PrintMessage("Initial clone complete...\n")
|
||||
if current_thread.isInterruptionRequested():
|
||||
return
|
||||
|
||||
if current_thread.isInterruptionRequested():
|
||||
return
|
||||
# Make sure to clone all the submodules as well
|
||||
if repo.submodules:
|
||||
FreeCAD.Console.PrintMessage("Updating submodules...\n")
|
||||
repo.submodule_update(recursive=True)
|
||||
|
||||
if self.repo.branch in repo.heads:
|
||||
repo.heads[self.repo.branch].checkout()
|
||||
if current_thread.isInterruptionRequested():
|
||||
return
|
||||
|
||||
if self.repo.branch in repo.heads:
|
||||
FreeCAD.Console.PrintMessage("Checking out HEAD...\n")
|
||||
repo.heads[self.repo.branch].checkout()
|
||||
|
||||
FreeCAD.Console.PrintMessage("Clone complete\n")
|
||||
|
||||
answer = translate(
|
||||
"AddonsInstaller",
|
||||
@@ -1465,38 +1331,6 @@ class DependencyInstallationWorker(QtCore.QThread):
|
||||
self.success.emit()
|
||||
|
||||
|
||||
class CheckSingleWorker(QtCore.QThread):
|
||||
"""Worker to check for updates for a single addon"""
|
||||
|
||||
updateAvailable = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, name):
|
||||
|
||||
QtCore.QThread.__init__(self)
|
||||
self.name = name
|
||||
|
||||
def run(self):
|
||||
|
||||
if not have_git or NOGIT:
|
||||
return
|
||||
FreeCAD.Console.PrintLog(
|
||||
"Checking for available updates of the " + self.name + " addon\n"
|
||||
)
|
||||
addondir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", self.name)
|
||||
if os.path.exists(addondir):
|
||||
if os.path.exists(addondir + os.sep + ".git"):
|
||||
gitrepo = git.Git(addondir)
|
||||
try:
|
||||
gitrepo.fetch()
|
||||
if "git pull" in gitrepo.status():
|
||||
self.updateAvailable.emit(True)
|
||||
return
|
||||
except Exception:
|
||||
# can fail for any number of reasons, ex. not being online
|
||||
pass
|
||||
self.updateAvailable.emit(False)
|
||||
|
||||
|
||||
class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
"Scan through all available packages and see if our local copy of package.xml needs to be updated"
|
||||
|
||||
|
||||
93
src/Mod/AddonManager/loading.html
Normal file
93
src/Mod/AddonManager/loading.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
Adapted from:
|
||||
https://codepen.io/MattIn4D/pen/LiKFC
|
||||
|
||||
Copyright © 2021 MattIn4D
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
/* Absolute Center Spinner */
|
||||
.loading {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
overflow: visible;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* Transparent Overlay */
|
||||
.loading:before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* :not(:required) hides these rules from IE9 and below */
|
||||
.loading:not(:required) {
|
||||
/* hide "loading..." text */
|
||||
font: 0/0 a;
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.loading:not(:required):after {
|
||||
content: '';
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: -0.5em;
|
||||
animation: spinner 5000ms infinite linear;
|
||||
border-radius: 0.5em;
|
||||
box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) -1.5em 0 0 0, rgba(0, 0, 0, 0.75) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loading">Loading...</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,21 +24,20 @@
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtWebEngineWidgets import *
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from datetime import date, timedelta
|
||||
|
||||
import FreeCAD
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_workers import ShowWorker, GetMacroDetailsWorker
|
||||
from addonmanager_workers import GetMacroDetailsWorker, CheckSingleUpdateWorker
|
||||
from AddonManagerRepo import AddonManagerRepo
|
||||
|
||||
import inspect
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
show_javascript_console_output = False
|
||||
|
||||
|
||||
class PackageDetails(QWidget):
|
||||
|
||||
@@ -57,9 +56,10 @@ class PackageDetails(QWidget):
|
||||
|
||||
self.worker = None
|
||||
self.repo = None
|
||||
self.status_update_thread = None
|
||||
|
||||
self.ui.buttonBack.clicked.connect(self.back.emit)
|
||||
self.ui.buttonRefresh.clicked.connect(self.refresh)
|
||||
self.ui.buttonBack.clicked.connect(self.clear_web_view)
|
||||
self.ui.buttonExecute.clicked.connect(lambda: self.execute.emit(self.repo))
|
||||
self.ui.buttonInstall.clicked.connect(lambda: self.install.emit(self.repo))
|
||||
self.ui.buttonUninstall.clicked.connect(lambda: self.uninstall.emit(self.repo))
|
||||
@@ -67,34 +67,64 @@ class PackageDetails(QWidget):
|
||||
self.ui.buttonCheckForUpdate.clicked.connect(
|
||||
lambda: self.check_for_update.emit(self.repo)
|
||||
)
|
||||
self.ui.webView.loadStarted.connect(self.load_started)
|
||||
self.ui.webView.loadProgress.connect(self.load_progress)
|
||||
self.ui.webView.loadFinished.connect(self.load_finished)
|
||||
|
||||
loading_html_file = os.path.join(os.path.dirname(__file__), "loading.html")
|
||||
with open(loading_html_file, "r", errors="ignore") as f:
|
||||
html = f.read()
|
||||
self.ui.loadingLabel.setHtml(html)
|
||||
self.ui.loadingLabel.show()
|
||||
self.ui.webView.hide()
|
||||
|
||||
def show_repo(self, repo: AddonManagerRepo, reload: bool = False) -> None:
|
||||
|
||||
self.repo = repo
|
||||
# If this is the same repo we were already showing, we do not have to do the
|
||||
# expensive refetch unless reload is true
|
||||
if self.repo != repo or reload:
|
||||
self.repo = repo
|
||||
self.ui.loadingLabel.show()
|
||||
self.ui.webView.hide()
|
||||
self.ui.progressBar.show()
|
||||
|
||||
if self.worker is not None:
|
||||
if not self.worker.isFinished():
|
||||
self.worker.requestInterruption()
|
||||
self.worker.wait()
|
||||
if self.worker is not None:
|
||||
if not self.worker.isFinished():
|
||||
self.worker.requestInterruption()
|
||||
self.worker.wait()
|
||||
|
||||
# Always load bare macros from scratch, we need to grab their code, which isn't cached
|
||||
force_reload = reload
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
force_reload = True
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
self.show_macro(repo)
|
||||
self.ui.buttonExecute.show()
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH:
|
||||
self.show_workbench(repo)
|
||||
self.ui.buttonExecute.hide()
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
self.show_package(repo)
|
||||
self.ui.buttonExecute.hide()
|
||||
|
||||
self.check_and_clean_cache(force_reload)
|
||||
if self.status_update_thread is not None:
|
||||
if not self.status_update_thread.isFinished():
|
||||
self.status_update_thread.requestInterruption()
|
||||
self.status_update_thread.wait()
|
||||
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
self.show_macro(repo)
|
||||
self.ui.buttonExecute.show()
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH:
|
||||
self.show_workbench(repo)
|
||||
self.ui.buttonExecute.hide()
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
self.show_package(repo)
|
||||
self.ui.buttonExecute.hide()
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
self.status_update_thread = QThread()
|
||||
self.status_update_worker = CheckSingleUpdateWorker(repo, self)
|
||||
self.status_update_worker.moveToThread(self.status_update_thread)
|
||||
self.status_update_thread.finished.connect(
|
||||
self.status_update_worker.deleteLater
|
||||
)
|
||||
self.check_for_update.connect(self.status_update_worker.do_work)
|
||||
self.status_update_worker.update_status.connect(self.display_repo_status)
|
||||
self.status_update_thread.start()
|
||||
self.check_for_update.emit(self.repo)
|
||||
|
||||
if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
self.display_repo_status(self.repo.update_status)
|
||||
|
||||
def display_repo_status(self, status):
|
||||
repo = self.repo
|
||||
if status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
|
||||
version = repo.installed_version
|
||||
date = ""
|
||||
@@ -125,7 +155,7 @@ class PackageDetails(QWidget):
|
||||
translate("AddonsInstaller", "Installed") + ". "
|
||||
)
|
||||
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
if status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
if repo.metadata:
|
||||
installed_version_string += (
|
||||
"<b>"
|
||||
@@ -151,21 +181,19 @@ class PackageDetails(QWidget):
|
||||
)
|
||||
+ ".</b>"
|
||||
)
|
||||
elif (
|
||||
repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE
|
||||
):
|
||||
elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
installed_version_string += (
|
||||
translate("AddonsInstaller", "This is the latest version available")
|
||||
+ "."
|
||||
)
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
installed_version_string += (
|
||||
translate(
|
||||
"AddonsInstaller", "Updated, please restart FreeCAD to use"
|
||||
)
|
||||
+ "."
|
||||
)
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
elif status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
autocheck = pref.GetBool("AutoCheck", False)
|
||||
@@ -181,7 +209,7 @@ class PackageDetails(QWidget):
|
||||
|
||||
installed_version_string += "</h3>"
|
||||
self.ui.labelPackageDetails.setText(installed_version_string)
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
self.ui.labelPackageDetails.setStyleSheet(
|
||||
"color:" + utils.attention_color_string()
|
||||
)
|
||||
@@ -206,27 +234,27 @@ class PackageDetails(QWidget):
|
||||
self.ui.labelPackageDetails.hide()
|
||||
self.ui.labelInstallationLocation.hide()
|
||||
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
self.ui.buttonInstall.show()
|
||||
self.ui.buttonUninstall.hide()
|
||||
self.ui.buttonUpdate.hide()
|
||||
self.ui.buttonCheckForUpdate.hide()
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
self.ui.buttonInstall.hide()
|
||||
self.ui.buttonUninstall.show()
|
||||
self.ui.buttonUpdate.hide()
|
||||
self.ui.buttonCheckForUpdate.hide()
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
elif status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
self.ui.buttonInstall.hide()
|
||||
self.ui.buttonUninstall.show()
|
||||
self.ui.buttonUpdate.show()
|
||||
self.ui.buttonCheckForUpdate.hide()
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
elif status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
self.ui.buttonInstall.hide()
|
||||
self.ui.buttonUninstall.show()
|
||||
self.ui.buttonUpdate.hide()
|
||||
self.ui.buttonCheckForUpdate.show()
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
self.ui.buttonInstall.hide()
|
||||
self.ui.buttonUninstall.show()
|
||||
self.ui.buttonUpdate.hide()
|
||||
@@ -255,127 +283,155 @@ class PackageDetails(QWidget):
|
||||
else:
|
||||
self.ui.labelWarningInfo.hide()
|
||||
|
||||
@classmethod
|
||||
def cache_path(self, repo: AddonManagerRepo) -> str:
|
||||
cache_path = FreeCAD.getUserCachePath()
|
||||
full_path = os.path.join(cache_path, "AddonManager", repo.name)
|
||||
return full_path
|
||||
|
||||
def check_and_clean_cache(self, force: bool = False) -> None:
|
||||
cache_path = PackageDetails.cache_path(self.repo)
|
||||
readme_cache_file = os.path.join(cache_path, "README.html")
|
||||
readme_images_path = os.path.join(cache_path, "Images")
|
||||
download_interrupted_sentinel = os.path.join(
|
||||
readme_images_path, "download_in_progress"
|
||||
)
|
||||
download_interrupted = os.path.isfile(download_interrupted_sentinel)
|
||||
if os.path.isfile(readme_cache_file):
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
days_between_updates = pref.GetInt("DaysBetweenUpdates", 2 ^ 32)
|
||||
timestamp = os.path.getmtime(readme_cache_file)
|
||||
last_cache_update = date.fromtimestamp(timestamp)
|
||||
delta_update = timedelta(days=days_between_updates)
|
||||
if (
|
||||
date.today() >= last_cache_update + delta_update
|
||||
or download_interrupted
|
||||
or force
|
||||
):
|
||||
if force:
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Forced README cache update for {self.repo.name}\n"
|
||||
)
|
||||
elif download_interrupted:
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Restarting interrupted README download for {self.repo.name}\n"
|
||||
)
|
||||
else:
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Cache expired, downloading README for {self.repo.name} again\n"
|
||||
)
|
||||
os.remove(readme_cache_file)
|
||||
if os.path.isdir(readme_images_path):
|
||||
shutil.rmtree(readme_images_path)
|
||||
|
||||
def refresh(self):
|
||||
self.check_and_clean_cache(force=True)
|
||||
self.show_repo(self.repo)
|
||||
|
||||
def show_cached_readme(self, repo: AddonManagerRepo) -> bool:
|
||||
"""Attempts to show a cached readme, returns true if there was a cache, or false if not"""
|
||||
|
||||
cache_path = PackageDetails.cache_path(repo)
|
||||
readme_cache_file = os.path.join(cache_path, "README.html")
|
||||
if os.path.isfile(readme_cache_file):
|
||||
with open(readme_cache_file, "rb") as f:
|
||||
data = f.read()
|
||||
self.ui.textBrowserReadMe.setText(data.decode())
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_workbench(self, repo: AddonManagerRepo) -> None:
|
||||
"""loads information of a given workbench"""
|
||||
|
||||
if not self.show_cached_readme(repo):
|
||||
self.ui.textBrowserReadMe.setText(
|
||||
translate(
|
||||
"AddonsInstaller", "Fetching README.md from package repository"
|
||||
)
|
||||
)
|
||||
self.worker = ShowWorker(repo, PackageDetails.cache_path(repo))
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.cache_readme(repo, desc)
|
||||
)
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.ui.textBrowserReadMe.setText(desc)
|
||||
)
|
||||
self.worker.update_status.connect(self.update_status.emit)
|
||||
self.worker.update_status.connect(self.show)
|
||||
self.worker.start()
|
||||
url = utils.get_readme_html_url(repo)
|
||||
self.ui.webView.load(QUrl(url))
|
||||
self.ui.urlBar.setText(url)
|
||||
|
||||
def show_package(self, repo: AddonManagerRepo) -> None:
|
||||
"""Show the details for a package (a repo with a package.xml metadata file)"""
|
||||
|
||||
if not self.show_cached_readme(repo):
|
||||
self.ui.textBrowserReadMe.setText(
|
||||
translate(
|
||||
"AddonsInstaller", "Fetching README.md from package repository"
|
||||
)
|
||||
)
|
||||
self.worker = ShowWorker(repo, PackageDetails.cache_path(repo))
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.cache_readme(repo, desc)
|
||||
)
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.ui.textBrowserReadMe.setText(desc)
|
||||
)
|
||||
self.worker.update_status.connect(self.update_status.emit)
|
||||
self.worker.update_status.connect(self.show)
|
||||
self.worker.start()
|
||||
readme_url = None
|
||||
if repo.metadata:
|
||||
urls = repo.metadata.Urls
|
||||
for url in urls:
|
||||
if url["type"] == "readme":
|
||||
readme_url = url["location"]
|
||||
break
|
||||
if not readme_url:
|
||||
readme_url = utils.get_readme_html_url(repo)
|
||||
self.ui.webView.load(QUrl(readme_url))
|
||||
self.ui.urlBar.setText(readme_url)
|
||||
|
||||
def show_macro(self, repo: AddonManagerRepo) -> None:
|
||||
"""loads information of a given macro"""
|
||||
|
||||
if not self.show_cached_readme(repo):
|
||||
self.ui.textBrowserReadMe.setText(
|
||||
translate(
|
||||
"AddonsInstaller", "Fetching README.md from package repository"
|
||||
)
|
||||
)
|
||||
self.worker = GetMacroDetailsWorker(repo)
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.cache_readme(repo, desc)
|
||||
)
|
||||
self.worker.readme_updated.connect(
|
||||
lambda desc: self.ui.textBrowserReadMe.setText(desc)
|
||||
)
|
||||
self.worker.start()
|
||||
self.ui.webView.load(QUrl(repo.macro.url))
|
||||
self.ui.urlBar.setText(repo.macro.url)
|
||||
|
||||
def cache_readme(self, repo: AddonManagerRepo, readme: str) -> None:
|
||||
cache_path = PackageDetails.cache_path(repo)
|
||||
readme_cache_file = os.path.join(cache_path, "README.html")
|
||||
os.makedirs(cache_path, exist_ok=True)
|
||||
with open(readme_cache_file, "wb") as f:
|
||||
f.write(readme.encode())
|
||||
# We need to populate the macro information... may as well do it while the user reads the wiki page
|
||||
self.worker = GetMacroDetailsWorker(repo)
|
||||
self.worker.start()
|
||||
|
||||
def run_javascript(self):
|
||||
"""Modify the page for a README to optimize for viewing in a smaller window"""
|
||||
|
||||
s = """
|
||||
( function() {
|
||||
const url = new URL (window.location);
|
||||
const body = document.getElementsByTagName("body")[0];
|
||||
if (url.hostname === "github.com") {
|
||||
const articles = document.getElementsByTagName("article");
|
||||
if (articles.length > 0) {
|
||||
const article = articles[0];
|
||||
body.appendChild (article);
|
||||
body.style.padding = "1em";
|
||||
let sibling = article.previousSibling;
|
||||
while (sibling) {
|
||||
sibling.remove();
|
||||
sibling = article.previousSibling;
|
||||
}
|
||||
}
|
||||
} else if (url.hostname === "gitlab.com" ||
|
||||
url.hostname === "framagit.org" ||
|
||||
url.hostname === "salsa.debian.org") {
|
||||
// These all use the GitLab page display...
|
||||
const articles = document.getElementsByTagName("article");
|
||||
if (articles.length > 0) {
|
||||
const article = articles[0];
|
||||
body.appendChild (article);
|
||||
body.style.padding = "1em";
|
||||
let sibling = article.previousSibling;
|
||||
while (sibling) {
|
||||
sibling.remove();
|
||||
sibling = article.previousSibling;
|
||||
}
|
||||
}
|
||||
} else if (url.hostname === "wiki.freecad.org" ||
|
||||
url.hostname === "wiki.freecadweb.org") {
|
||||
const first_heading = document.getElementById('firstHeading');
|
||||
const body_content = document.getElementById('bodyContent');
|
||||
const new_node = document.createElement("div");
|
||||
new_node.appendChild(first_heading);
|
||||
new_node.appendChild(body_content);
|
||||
body.appendChild(new_node);
|
||||
let sibling = new_node.previousSibling;
|
||||
while (sibling) {
|
||||
sibling.remove();
|
||||
sibling = new_node.previousSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
) ()
|
||||
"""
|
||||
self.ui.webView.page().runJavaScript(s)
|
||||
|
||||
def clear_web_view(self):
|
||||
self.ui.webView.setHtml("<html><body><h1>Loading...</h1></body></html>")
|
||||
|
||||
def load_started(self):
|
||||
self.ui.progressBar.show()
|
||||
self.ui.progressBar.setValue(0)
|
||||
|
||||
def load_progress(self, progress: int):
|
||||
self.ui.progressBar.setValue(progress)
|
||||
|
||||
def load_finished(self, load_succeeded: bool):
|
||||
self.ui.loadingLabel.hide()
|
||||
self.ui.webView.show()
|
||||
self.ui.progressBar.hide()
|
||||
url = self.ui.webView.url()
|
||||
if load_succeeded:
|
||||
# It says it succeeded, but it might have only succeeded in loading a "Page not found" page!
|
||||
title = self.ui.webView.title()
|
||||
path_components = url.path().split("/")
|
||||
expected_content = path_components[-1]
|
||||
if url.host() == "github.com" and expected_content not in title:
|
||||
self.show_error_for(url)
|
||||
elif title == "":
|
||||
self.show_error_for(url)
|
||||
else:
|
||||
self.run_javascript()
|
||||
else:
|
||||
self.show_error_for(url)
|
||||
|
||||
def show_error_for(self, url: QUrl) -> None:
|
||||
m = translate(
|
||||
"AddonsInstaller", "Could not load README data from URL {}"
|
||||
).format(url.toString())
|
||||
html = f"<html><body><p>{m}</p></body></html>"
|
||||
self.ui.webView.setHtml(html)
|
||||
|
||||
|
||||
class RestrictedWebPage(QWebEnginePage):
|
||||
"""A class that follows links to FreeCAD wiki pages, but opens all other clicked links in the system web browser"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.settings().setAttribute(QWebEngineSettings.ErrorPageEnabled, False)
|
||||
|
||||
def acceptNavigationRequest(self, url, _type, isMainFrame):
|
||||
if _type == QWebEnginePage.NavigationTypeLinkClicked:
|
||||
|
||||
# See if the link is to a FreeCAD Wiki page -- if so, follow it, otherwise ask the OS to open it
|
||||
if url.host() == "wiki.freecad.org" or url.host() == "wiki.freecadweb.org":
|
||||
return super().acceptNavigationRequest(url, _type, isMainFrame)
|
||||
else:
|
||||
QDesktopServices.openUrl(url)
|
||||
return False
|
||||
return super().acceptNavigationRequest(url, _type, isMainFrame)
|
||||
|
||||
def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
|
||||
global show_javascript_console_output
|
||||
if show_javascript_console_output:
|
||||
tag = translate("AddonsInstaller", "Page JavaScript reported")
|
||||
if level == QWebEnginePage.InfoMessageLevel:
|
||||
FreeCAD.Console.PrintMessage(f"{tag} {lineNumber}: {message}\n")
|
||||
elif level == QWebEnginePage.WarningMessageLevel:
|
||||
FreeCAD.Console.PrintWarning(f"{tag} {lineNumber}: {message}\n")
|
||||
elif level == QWebEnginePage.ErrorMessageLevel:
|
||||
FreeCAD.Console.PrintError(f"{tag} {lineNumber}: {message}\n")
|
||||
|
||||
|
||||
class Ui_PackageDetails(object):
|
||||
@@ -391,14 +447,8 @@ class Ui_PackageDetails(object):
|
||||
self.buttonBack.setIcon(
|
||||
QIcon.fromTheme("back", QIcon(":/icons/button_left.svg"))
|
||||
)
|
||||
self.buttonRefresh = QToolButton(PackageDetails)
|
||||
self.buttonRefresh.setObjectName("buttonRefresh")
|
||||
self.buttonRefresh.setIcon(
|
||||
QIcon.fromTheme("refresh", QIcon(":/icons/view-refresh.svg"))
|
||||
)
|
||||
|
||||
self.layoutDetailsBackButton.addWidget(self.buttonBack)
|
||||
self.layoutDetailsBackButton.addWidget(self.buttonRefresh)
|
||||
|
||||
self.horizontalSpacer = QSpacerItem(
|
||||
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
|
||||
@@ -449,12 +499,34 @@ class Ui_PackageDetails(object):
|
||||
|
||||
self.verticalLayout_2.addWidget(self.labelWarningInfo)
|
||||
|
||||
self.textBrowserReadMe = QTextBrowser(PackageDetails)
|
||||
self.textBrowserReadMe.setObjectName("textBrowserReadMe")
|
||||
self.textBrowserReadMe.setOpenExternalLinks(True)
|
||||
self.textBrowserReadMe.setOpenLinks(True)
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
|
||||
self.verticalLayout_2.addWidget(self.textBrowserReadMe)
|
||||
self.webView = QWebEngineView(PackageDetails)
|
||||
self.webView.setObjectName("webView")
|
||||
self.webView.setSizePolicy(sizePolicy1)
|
||||
self.webView.setPage(RestrictedWebPage(PackageDetails))
|
||||
|
||||
self.verticalLayout_2.addWidget(self.webView)
|
||||
|
||||
self.loadingLabel = QWebEngineView(PackageDetails)
|
||||
self.loadingLabel.setObjectName("loadingLabel")
|
||||
self.loadingLabel.setSizePolicy(sizePolicy1)
|
||||
|
||||
self.verticalLayout_2.addWidget(self.loadingLabel)
|
||||
|
||||
self.progressBar = QProgressBar(PackageDetails)
|
||||
self.progressBar.setObjectName("progressBar")
|
||||
self.progressBar.setTextVisible(False)
|
||||
|
||||
self.verticalLayout_2.addWidget(self.progressBar)
|
||||
|
||||
self.urlBar = QLineEdit(PackageDetails)
|
||||
self.urlBar.setObjectName("urlBar")
|
||||
self.urlBar.setReadOnly(True)
|
||||
|
||||
self.verticalLayout_2.addWidget(self.urlBar)
|
||||
|
||||
self.retranslateUi(PackageDetails)
|
||||
|
||||
@@ -462,7 +534,7 @@ class Ui_PackageDetails(object):
|
||||
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, PackageDetails):
|
||||
def retranslateUi(self, _):
|
||||
self.buttonBack.setText("")
|
||||
self.buttonInstall.setText(
|
||||
QCoreApplication.translate("AddonsInstaller", "Install", None)
|
||||
@@ -484,12 +556,5 @@ class Ui_PackageDetails(object):
|
||||
"AddonsInstaller", "Return to package list", None
|
||||
)
|
||||
)
|
||||
self.buttonRefresh.setToolTip(
|
||||
QCoreApplication.translate(
|
||||
"AddonsInstaller",
|
||||
"Delete cached version of this README and re-download",
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
# retranslateUi
|
||||
|
||||
@@ -226,7 +226,7 @@ class PackageListItemModel(QAbstractListModel):
|
||||
row = index.row()
|
||||
self.write_lock.acquire()
|
||||
if role == PackageListItemModel.StatusUpdateRole:
|
||||
self.repos[row].update_status = value
|
||||
self.repos[row].set_status(value)
|
||||
self.dataChanged.emit(
|
||||
self.index(row, 2),
|
||||
self.index(row, 2),
|
||||
@@ -408,13 +408,13 @@ class PackageListItemDelegate(QStyledItemDelegate):
|
||||
"""Get a single-line string listing details about the installed version and date"""
|
||||
|
||||
result = ""
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
result = translate("AddonsInstaller", "Installed")
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
result = translate("AddonsInstaller", "Up-to-date")
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
result = translate("AddonsInstaller", "Update available")
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
result = translate("AddonsInstaller", "Pending restart")
|
||||
return result
|
||||
|
||||
@@ -424,7 +424,7 @@ class PackageListItemDelegate(QStyledItemDelegate):
|
||||
result = ""
|
||||
|
||||
installed_version_string = ""
|
||||
if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if repo.status() != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if repo.installed_version:
|
||||
installed_version_string = (
|
||||
"\n" + translate("AddonsInstaller", "Installed version") + ": "
|
||||
@@ -453,20 +453,20 @@ class PackageListItemDelegate(QStyledItemDelegate):
|
||||
)
|
||||
available_version_string += repo.metadata.Version
|
||||
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED:
|
||||
result = translate("AddonsInstaller", "Installed")
|
||||
result += installed_version_string
|
||||
result += installed_date_string
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
|
||||
result = translate("AddonsInstaller", "Up-to-date")
|
||||
result += installed_version_string
|
||||
result += installed_date_string
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
result = translate("AddonsInstaller", "Update available")
|
||||
result += installed_version_string
|
||||
result += installed_date_string
|
||||
result += available_version_string
|
||||
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
elif repo.status() == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
|
||||
result = translate("AddonsInstaller", "Pending restart")
|
||||
|
||||
return result
|
||||
@@ -530,18 +530,18 @@ class PackageListFilter(QSortFilterProxyModel):
|
||||
return False
|
||||
|
||||
if self.status == StatusFilter.INSTALLED:
|
||||
if data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
return False
|
||||
elif self.status == StatusFilter.NOT_INSTALLED:
|
||||
if data.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
if data.status() != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
|
||||
return False
|
||||
elif self.status == StatusFilter.UPDATE_AVAILABLE:
|
||||
if data.update_status != AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
if data.status() != AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
return False
|
||||
|
||||
# If it's not installed, check to see if it's Py2 only
|
||||
if (
|
||||
data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
and self.hide_py2
|
||||
and data.python2
|
||||
):
|
||||
@@ -549,7 +549,7 @@ class PackageListFilter(QSortFilterProxyModel):
|
||||
|
||||
# If it's not installed, check to see if it's marked obsolete
|
||||
if (
|
||||
data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
data.status() == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
and self.hide_obsolete
|
||||
and data.obsolete
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user