Addon Manager: Black reformatting
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#***************************************************************************
|
||||
#* *
|
||||
#* Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
|
||||
#* Copyright (c) 2021 Chris Hennes <chennes@pioneerlibrarysystem.org> *
|
||||
#* *
|
||||
#* This program is free software; you can redistribute it and/or modify *
|
||||
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
#* as published by the Free Software Foundation; either version 2 of *
|
||||
#* the License, or (at your option) any later version. *
|
||||
#* for detail see the LICENCE text file. *
|
||||
#* *
|
||||
#* This program is distributed in the hope that it will be useful, *
|
||||
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
#* GNU Library General Public License for more details. *
|
||||
#* *
|
||||
#* You should have received a copy of the GNU Library General Public *
|
||||
#* License along with this program; if not, write to the Free Software *
|
||||
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
#* USA *
|
||||
#* *
|
||||
#***************************************************************************
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2021 Chris Hennes <chennes@pioneerlibrarysystem.org> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@@ -70,44 +70,68 @@ installed.
|
||||
def QT_TRANSLATE_NOOP(ctx, txt):
|
||||
return txt
|
||||
|
||||
|
||||
class CommandAddonManager:
|
||||
"""The main Addon Manager class and FreeCAD command"""
|
||||
|
||||
workers = ["update_worker", "check_worker", "show_worker",
|
||||
"showmacro_worker", "macro_worker", "install_worker",
|
||||
"update_metadata_cache_worker", "update_all_worker"]
|
||||
workers = [
|
||||
"update_worker",
|
||||
"check_worker",
|
||||
"show_worker",
|
||||
"showmacro_worker",
|
||||
"macro_worker",
|
||||
"install_worker",
|
||||
"update_metadata_cache_worker",
|
||||
"update_all_worker",
|
||||
]
|
||||
|
||||
lock = threading.Lock()
|
||||
restart_required = False
|
||||
|
||||
def __init__(self):
|
||||
FreeCADGui.addPreferencePage(os.path.join(os.path.dirname(__file__),
|
||||
"AddonManagerOptions.ui"),"Addon Manager")
|
||||
FreeCADGui.addPreferencePage(
|
||||
os.path.join(os.path.dirname(__file__), "AddonManagerOptions.ui"),
|
||||
"Addon Manager",
|
||||
)
|
||||
|
||||
def GetResources(self) -> Dict[str,str]:
|
||||
return {"Pixmap": "AddonManager",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Std_AddonMgr", "&Addon manager"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Std_AddonMgr", "Manage external workbenches, macros, and preference packs"),
|
||||
"Group": "Tools"}
|
||||
def GetResources(self) -> Dict[str, str]:
|
||||
return {
|
||||
"Pixmap": "AddonManager",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Std_AddonMgr", "&Addon manager"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Std_AddonMgr",
|
||||
"Manage external workbenches, macros, and preference packs",
|
||||
),
|
||||
"Group": "Tools",
|
||||
}
|
||||
|
||||
def Activated(self) -> None:
|
||||
|
||||
# display first use dialog if needed
|
||||
readWarningParameter = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
readWarningParameter = FreeCAD.ParamGet(
|
||||
"User parameter:BaseApp/Preferences/Addons"
|
||||
)
|
||||
readWarning = readWarningParameter.GetBool("readWarning", False)
|
||||
newReadWarningParameter = FreeCAD.ParamGet("User parameter:Plugins/addonsRepository")
|
||||
newReadWarningParameter = FreeCAD.ParamGet(
|
||||
"User parameter:Plugins/addonsRepository"
|
||||
)
|
||||
readWarning |= newReadWarningParameter.GetBool("readWarning", False)
|
||||
if not readWarning:
|
||||
if (QtWidgets.QMessageBox.warning(None,
|
||||
"FreeCAD",
|
||||
translate("AddonsInstaller",
|
||||
"The addons that can be installed here are not "
|
||||
"officially part of FreeCAD, and are not reviewed "
|
||||
"by the FreeCAD team. Make sure you know what you "
|
||||
"are installing!"),
|
||||
QtWidgets.QMessageBox.Cancel |
|
||||
QtWidgets.QMessageBox.Ok) !=
|
||||
QtWidgets.QMessageBox.StandardButton.Cancel):
|
||||
if (
|
||||
QtWidgets.QMessageBox.warning(
|
||||
None,
|
||||
"FreeCAD",
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"The addons that can be installed here are not "
|
||||
"officially part of FreeCAD, and are not reviewed "
|
||||
"by the FreeCAD team. Make sure you know what you "
|
||||
"are installing!",
|
||||
),
|
||||
QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
!= QtWidgets.QMessageBox.StandardButton.Cancel
|
||||
):
|
||||
readWarningParameter.SetBool("readWarning", True)
|
||||
readWarning = True
|
||||
|
||||
@@ -118,8 +142,9 @@ class CommandAddonManager:
|
||||
"""Shows the Addon Manager UI"""
|
||||
|
||||
# create the dialog
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),
|
||||
"AddonManager.ui"))
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(
|
||||
os.path.join(os.path.dirname(__file__), "AddonManager.ui")
|
||||
)
|
||||
|
||||
# cleanup the leftovers from previous runs
|
||||
self.macro_repo_dir = FreeCAD.getUserMacroDir()
|
||||
@@ -151,7 +176,7 @@ class CommandAddonManager:
|
||||
days_between_updates = pref.GetInt("DaysBetweenUpdates", days_between_updates)
|
||||
last_cache_update_string = pref.GetString("LastCacheUpdate", "never")
|
||||
cache_path = FreeCAD.getUserCachePath()
|
||||
am_path = os.path.join(cache_path,"AddonManager")
|
||||
am_path = os.path.join(cache_path, "AddonManager")
|
||||
if last_cache_update_string == "never":
|
||||
self.update_cache = True
|
||||
elif days_between_updates > 0:
|
||||
@@ -169,7 +194,9 @@ class CommandAddonManager:
|
||||
self.item_model = PackageListItemModel()
|
||||
self.packageList.setModel(self.item_model)
|
||||
self.dialog.contentPlaceholder.hide()
|
||||
self.dialog.layout().replaceWidget(self.dialog.contentPlaceholder, self.packageList)
|
||||
self.dialog.layout().replaceWidget(
|
||||
self.dialog.contentPlaceholder, self.packageList
|
||||
)
|
||||
self.packageList.show()
|
||||
|
||||
# Package details start out hidden
|
||||
@@ -181,8 +208,14 @@ class CommandAddonManager:
|
||||
# set nice icons to everything, by theme with fallback to FreeCAD icons
|
||||
self.dialog.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
|
||||
self.dialog.buttonUpdateAll.setIcon(QtGui.QIcon(":/icons/button_valid.svg"))
|
||||
self.dialog.buttonClose.setIcon(QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")))
|
||||
self.dialog.buttonPauseUpdate.setIcon(QtGui.QIcon.fromTheme("pause", QtGui.QIcon(":/icons/media-playback-stop.svg")))
|
||||
self.dialog.buttonClose.setIcon(
|
||||
QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg"))
|
||||
)
|
||||
self.dialog.buttonPauseUpdate.setIcon(
|
||||
QtGui.QIcon.fromTheme(
|
||||
"pause", QtGui.QIcon(":/icons/media-playback-stop.svg")
|
||||
)
|
||||
)
|
||||
|
||||
# enable/disable stuff
|
||||
self.dialog.buttonUpdateAll.setEnabled(False)
|
||||
@@ -206,10 +239,14 @@ class CommandAddonManager:
|
||||
|
||||
# center the dialog over the FreeCAD window
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
self.dialog.move(mw.frameGeometry().topLeft() + mw.rect().center() - self.dialog.rect().center())
|
||||
self.dialog.move(
|
||||
mw.frameGeometry().topLeft()
|
||||
+ mw.rect().center()
|
||||
- self.dialog.rect().center()
|
||||
)
|
||||
|
||||
# set info for the progress bar:
|
||||
self.dialog.progressBar.setMaximum (100)
|
||||
self.dialog.progressBar.setMaximum(100)
|
||||
|
||||
# begin populating the table in a set of sub-threads
|
||||
self.startup()
|
||||
@@ -221,7 +258,7 @@ class CommandAddonManager:
|
||||
self.dialog.exec_()
|
||||
|
||||
def cleanup_workers(self, wait=False) -> None:
|
||||
""" Ensure that no workers are running by explicitly asking them to stop and waiting for them until they do """
|
||||
"""Ensure that no workers are running by explicitly asking them to stop and waiting for them until they do"""
|
||||
for worker in self.workers:
|
||||
if hasattr(self, worker):
|
||||
thread = getattr(self, worker)
|
||||
@@ -283,26 +320,31 @@ class CommandAddonManager:
|
||||
m = QtWidgets.QMessageBox()
|
||||
m.setWindowTitle(translate("AddonsInstaller", "Addon manager"))
|
||||
m.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
|
||||
m.setText(translate("AddonsInstaller",
|
||||
"You must restart FreeCAD for changes to take "
|
||||
"effect."))
|
||||
m.setText(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"You must restart FreeCAD for changes to take " "effect.",
|
||||
)
|
||||
)
|
||||
m.setIcon(m.Warning)
|
||||
m.setStandardButtons(m.Ok | m.Cancel)
|
||||
m.setDefaultButton(m.Cancel)
|
||||
okBtn = m.button(QtWidgets.QMessageBox.StandardButton.Ok)
|
||||
cancelBtn = m.button(QtWidgets.QMessageBox.StandardButton.Cancel)
|
||||
okBtn.setText(translate("AddonsInstaller","Restart now"))
|
||||
cancelBtn.setText(translate("AddonsInstaller","Restart later"))
|
||||
okBtn.setText(translate("AddonsInstaller", "Restart now"))
|
||||
cancelBtn.setText(translate("AddonsInstaller", "Restart later"))
|
||||
ret = m.exec_()
|
||||
if ret == m.Ok:
|
||||
# restart FreeCAD after a delay to give time to this dialog to close
|
||||
QtCore.QTimer.singleShot(1000, utils.restart_freecad)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning("Could not terminate sub-threads in Addon Manager.\n")
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Could not terminate sub-threads in Addon Manager.\n"
|
||||
)
|
||||
self.cleanup_workers()
|
||||
|
||||
def startup(self) -> None:
|
||||
""" Downloads the available packages listings and populates the table
|
||||
"""Downloads the available packages listings and populates the table
|
||||
|
||||
This proceeds in four stages: first, the main GitHub repository is queried for a list of possible
|
||||
addons. Each addon is specified as a git submodule with name and branch information. The actual specific
|
||||
@@ -324,22 +366,24 @@ class CommandAddonManager:
|
||||
|
||||
"""
|
||||
|
||||
# Each function in this list is expected to launch a thread and connect its completion signal
|
||||
# Each function in this list is expected to launch a thread and connect its completion signal
|
||||
# to self.do_next_startup_phase, or to shortcut to calling self.do_next_startup_phase if it
|
||||
# is not launching a worker
|
||||
self.startup_sequence = [self.populate_packages_table,
|
||||
self.activate_table_widgets,
|
||||
self.populate_macros,
|
||||
self.update_metadata_cache,
|
||||
self.check_updates]
|
||||
self.startup_sequence = [
|
||||
self.populate_packages_table,
|
||||
self.activate_table_widgets,
|
||||
self.populate_macros,
|
||||
self.update_metadata_cache,
|
||||
self.check_updates,
|
||||
]
|
||||
self.current_progress_region = 0
|
||||
self.number_of_progress_regions = len(self.startup_sequence)
|
||||
self.do_next_startup_phase()
|
||||
|
||||
def do_next_startup_phase(self) -> None:
|
||||
""" Pop the top item in self.startup_sequence off the list and run it """
|
||||
"""Pop the top item in self.startup_sequence off the list and run it"""
|
||||
|
||||
if (len(self.startup_sequence) > 0):
|
||||
if len(self.startup_sequence) > 0:
|
||||
phase_runner = self.startup_sequence.pop(0)
|
||||
self.current_progress_region += 1
|
||||
phase_runner()
|
||||
@@ -349,11 +393,11 @@ class CommandAddonManager:
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
pref.SetString("LastCacheUpdate", date.today().isoformat())
|
||||
|
||||
def get_cache_file_name(self, file:str) -> str:
|
||||
def get_cache_file_name(self, file: str) -> str:
|
||||
cache_path = FreeCAD.getUserCachePath()
|
||||
am_path = os.path.join(cache_path,"AddonManager")
|
||||
os.makedirs(am_path,exist_ok=True)
|
||||
return os.path.join(am_path,file)
|
||||
am_path = os.path.join(cache_path, "AddonManager")
|
||||
os.makedirs(am_path, exist_ok=True)
|
||||
return os.path.join(am_path, file)
|
||||
|
||||
def populate_packages_table(self) -> None:
|
||||
self.item_model.clear()
|
||||
@@ -362,7 +406,7 @@ class CommandAddonManager:
|
||||
use_cache = not self.update_cache
|
||||
if use_cache:
|
||||
if os.path.isfile(self.get_cache_file_name("package_cache.json")):
|
||||
with open(self.get_cache_file_name("package_cache.json"),"r") as f:
|
||||
with open(self.get_cache_file_name("package_cache.json"), "r") as f:
|
||||
data = f.read()
|
||||
try:
|
||||
from_json = json.loads(data)
|
||||
@@ -374,28 +418,34 @@ class CommandAddonManager:
|
||||
use_cache = False
|
||||
|
||||
if not use_cache:
|
||||
self.update_cache = True # Make sure to trigger the other cache updates, if the json file was missing
|
||||
self.update_cache = True # Make sure to trigger the other cache updates, if the json file was missing
|
||||
self.update_worker = UpdateWorker()
|
||||
self.update_worker.status_message.connect(self.show_information)
|
||||
self.update_worker.addon_repo.connect(self.add_addon_repo)
|
||||
self.update_progress_bar(10,100)
|
||||
self.update_worker.done.connect(self.do_next_startup_phase) # Link to step 2
|
||||
self.update_progress_bar(10, 100)
|
||||
self.update_worker.done.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 2
|
||||
self.update_worker.start()
|
||||
else:
|
||||
self.update_worker = LoadPackagesFromCacheWorker(self.get_cache_file_name("package_cache.json"))
|
||||
self.update_worker = LoadPackagesFromCacheWorker(
|
||||
self.get_cache_file_name("package_cache.json")
|
||||
)
|
||||
self.update_worker.addon_repo.connect(self.add_addon_repo)
|
||||
self.update_progress_bar(10,100)
|
||||
self.update_worker.done.connect(self.do_next_startup_phase) # Link to step 2
|
||||
self.update_progress_bar(10, 100)
|
||||
self.update_worker.done.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 2
|
||||
self.update_worker.start()
|
||||
|
||||
def cache_package(self, repo:AddonManagerRepo):
|
||||
def cache_package(self, repo: AddonManagerRepo):
|
||||
if not hasattr(self, "package_cache"):
|
||||
self.package_cache = []
|
||||
self.package_cache.append(repo.to_cache())
|
||||
|
||||
def write_package_cache(self):
|
||||
package_cache_path = self.get_cache_file_name("package_cache.json")
|
||||
with open(package_cache_path,"w") as f:
|
||||
with open(package_cache_path, "w") as f:
|
||||
f.write(json.dumps(self.package_cache))
|
||||
self.package_cache = []
|
||||
|
||||
@@ -406,38 +456,52 @@ class CommandAddonManager:
|
||||
|
||||
def populate_macros(self) -> None:
|
||||
self.current_progress_region += 1
|
||||
if self.update_cache or not os.path.isfile(self.get_cache_file_name("macro_cache.json")):
|
||||
if self.update_cache or not os.path.isfile(
|
||||
self.get_cache_file_name("macro_cache.json")
|
||||
):
|
||||
self.macro_worker = FillMacroListWorker(self.get_cache_file_name("Macros"))
|
||||
self.macro_worker.status_message_signal.connect(self.show_information)
|
||||
self.macro_worker.progress_made.connect(self.update_progress_bar)
|
||||
self.macro_worker.add_macro_signal.connect(self.add_addon_repo)
|
||||
self.macro_worker.done.connect(self.do_next_startup_phase) # Link to step 3
|
||||
self.macro_worker.done.connect(self.do_next_startup_phase) # Link to step 3
|
||||
self.macro_worker.start()
|
||||
else:
|
||||
self.macro_worker = LoadMacrosFromCacheWorker(self.get_cache_file_name("macro_cache.json"))
|
||||
self.macro_worker = LoadMacrosFromCacheWorker(
|
||||
self.get_cache_file_name("macro_cache.json")
|
||||
)
|
||||
self.macro_worker.add_macro_signal.connect(self.add_addon_repo)
|
||||
self.macro_worker.done.connect(self.do_next_startup_phase) # Link to step 3
|
||||
self.macro_worker.done.connect(self.do_next_startup_phase) # Link to step 3
|
||||
self.macro_worker.start()
|
||||
|
||||
def cache_macro(self, macro:AddonManagerRepo):
|
||||
def cache_macro(self, macro: AddonManagerRepo):
|
||||
if not hasattr(self, "macro_cache"):
|
||||
self.macro_cache = []
|
||||
self.macro_cache.append(macro.macro.to_cache())
|
||||
|
||||
def write_macro_cache(self):
|
||||
macro_cache_path = self.get_cache_file_name("macro_cache.json")
|
||||
with open(macro_cache_path,"w") as f:
|
||||
with open(macro_cache_path, "w") as f:
|
||||
f.write(json.dumps(self.macro_cache))
|
||||
self.macro_cache = []
|
||||
|
||||
|
||||
def update_metadata_cache(self) -> None:
|
||||
self.current_progress_region += 1
|
||||
if self.update_cache:
|
||||
self.update_metadata_cache_worker = UpdateMetadataCacheWorker(self.item_model.repos)
|
||||
self.update_metadata_cache_worker.status_message.connect(self.show_information)
|
||||
self.update_metadata_cache_worker.done.connect(self.do_next_startup_phase) # Link to step 4
|
||||
self.update_metadata_cache_worker.progress_made.connect(self.update_progress_bar)
|
||||
self.update_metadata_cache_worker.package_updated.connect(self.on_package_updated)
|
||||
self.update_metadata_cache_worker = UpdateMetadataCacheWorker(
|
||||
self.item_model.repos
|
||||
)
|
||||
self.update_metadata_cache_worker.status_message.connect(
|
||||
self.show_information
|
||||
)
|
||||
self.update_metadata_cache_worker.done.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 4
|
||||
self.update_metadata_cache_worker.progress_made.connect(
|
||||
self.update_progress_bar
|
||||
)
|
||||
self.update_metadata_cache_worker.package_updated.connect(
|
||||
self.on_package_updated
|
||||
)
|
||||
self.update_metadata_cache_worker.start()
|
||||
else:
|
||||
self.do_next_startup_phase()
|
||||
@@ -446,18 +510,19 @@ class CommandAddonManager:
|
||||
self.update_cache = True
|
||||
self.startup()
|
||||
|
||||
def on_package_updated(self, repo:AddonManagerRepo) -> None:
|
||||
def on_package_updated(self, repo: AddonManagerRepo) -> None:
|
||||
"""Called when the named package has either new metadata or a new icon (or both)"""
|
||||
|
||||
|
||||
with self.lock:
|
||||
cache_path = os.path.join(FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata", repo.name)
|
||||
cache_path = os.path.join(
|
||||
FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata", repo.name
|
||||
)
|
||||
repo.icon = self.get_icon(repo, update=True)
|
||||
self.item_model.reload_item(repo)
|
||||
|
||||
|
||||
def check_updates(self) -> None:
|
||||
"checks every installed addon for available updates"
|
||||
|
||||
|
||||
self.current_progress_region += 1
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
autocheck = pref.GetBool("AutoCheck", False)
|
||||
@@ -471,7 +536,9 @@ class CommandAddonManager:
|
||||
if not thread.isFinished():
|
||||
self.do_next_startup_phase()
|
||||
return
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Checking for updates..."))
|
||||
self.dialog.buttonUpdateAll.setText(
|
||||
translate("AddonsInstaller", "Checking for updates...")
|
||||
)
|
||||
self.check_worker = CheckWorkbenchesForUpdatesWorker(self.item_model.repos)
|
||||
self.check_worker.done.connect(self.do_next_startup_phase)
|
||||
self.check_worker.progress_made.connect(self.update_progress_bar)
|
||||
@@ -479,36 +546,44 @@ 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: AddonManagerRepo) -> None:
|
||||
self.item_model.reload_item(repo)
|
||||
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
|
||||
self.packages_with_updates.append(repo)
|
||||
self.enable_updates(len(self.packages_with_updates))
|
||||
|
||||
def enable_updates(self, number_of_updates:int) -> None:
|
||||
def enable_updates(self, number_of_updates: int) -> None:
|
||||
"""enables the update button"""
|
||||
|
||||
if number_of_updates:
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Apply") +
|
||||
" " + str(number_of_updates) + " " +
|
||||
translate("AddonsInstaller", "update(s)"))
|
||||
self.dialog.buttonUpdateAll.setText(
|
||||
translate("AddonsInstaller", "Apply")
|
||||
+ " "
|
||||
+ str(number_of_updates)
|
||||
+ " "
|
||||
+ translate("AddonsInstaller", "update(s)")
|
||||
)
|
||||
self.dialog.buttonUpdateAll.setEnabled(True)
|
||||
else:
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "No updates available"))
|
||||
self.dialog.buttonUpdateAll.setText(
|
||||
translate("AddonsInstaller", "No updates available")
|
||||
)
|
||||
self.dialog.buttonUpdateAll.setEnabled(False)
|
||||
|
||||
def add_addon_repo(self, addon_repo:AddonManagerRepo) -> None:
|
||||
def add_addon_repo(self, addon_repo: AddonManagerRepo) -> None:
|
||||
"""adds a workbench to the list"""
|
||||
|
||||
if addon_repo.icon is None or addon_repo.icon.isNull():
|
||||
|
||||
if addon_repo.icon is None or addon_repo.icon.isNull():
|
||||
addon_repo.icon = self.get_icon(addon_repo)
|
||||
for repo in self.item_model.repos:
|
||||
if repo.name == addon_repo.name:
|
||||
FreeCAD.Console.PrintLog(f"Possible duplicate addon: ignoring second addition of {addon_repo.name}\n")
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Possible duplicate addon: ignoring second addition of {addon_repo.name}\n"
|
||||
)
|
||||
return
|
||||
self.item_model.append_item(addon_repo)
|
||||
|
||||
def get_icon(self, repo:AddonManagerRepo, update:bool=False) -> QtGui.QIcon:
|
||||
def get_icon(self, repo: AddonManagerRepo, 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():
|
||||
@@ -516,8 +591,8 @@ class CommandAddonManager:
|
||||
|
||||
path = ":/icons/" + repo.name.replace(" ", "_")
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH:
|
||||
path += "_workbench_icon.svg"
|
||||
default_icon = QtGui.QIcon(":/icons/document-package.svg")
|
||||
path += "_workbench_icon.svg"
|
||||
default_icon = QtGui.QIcon(":/icons/document-package.svg")
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
path += "_macro_icon.svg"
|
||||
default_icon = QtGui.QIcon(":/icons/document-python.svg")
|
||||
@@ -542,19 +617,19 @@ class CommandAddonManager:
|
||||
|
||||
return addonicon
|
||||
|
||||
def table_row_activated(self, selected_repo:AddonManagerRepo) -> None:
|
||||
def table_row_activated(self, selected_repo: AddonManagerRepo) -> None:
|
||||
"""a row was activated, show the relevant data"""
|
||||
|
||||
self.packageList.hide()
|
||||
self.packageDetails.show()
|
||||
self.packageDetails.show_repo(selected_repo)
|
||||
|
||||
def show_information(self, message:str) -> None:
|
||||
def show_information(self, message: str) -> None:
|
||||
"""shows generic text in the information pane (which might be collapsed)"""
|
||||
|
||||
self.dialog.labelStatusInfo.setText(message)
|
||||
|
||||
def show_workbench(self, repo:AddonManagerRepo) -> None:
|
||||
def show_workbench(self, repo: AddonManagerRepo) -> None:
|
||||
self.packageList.hide()
|
||||
self.packageDetails.show()
|
||||
self.packageDetails.show_repo(repo)
|
||||
@@ -563,12 +638,12 @@ class CommandAddonManager:
|
||||
self.packageDetails.hide()
|
||||
self.packageList.show()
|
||||
|
||||
def append_to_repos_list(self, repo:AddonManagerRepo) -> None:
|
||||
def append_to_repos_list(self, repo: AddonManagerRepo) -> None:
|
||||
"""this function allows threads to update the main list of workbenches"""
|
||||
|
||||
self.item_model.append_item(repo)
|
||||
|
||||
def install(self, repo:AddonManagerRepo) -> None:
|
||||
def install(self, repo: AddonManagerRepo) -> None:
|
||||
"""installs or updates a workbench, macro, or package"""
|
||||
|
||||
if hasattr(self, "install_worker") and self.install_worker:
|
||||
@@ -578,7 +653,10 @@ class CommandAddonManager:
|
||||
if not repo:
|
||||
return
|
||||
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH or repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
if (
|
||||
repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH
|
||||
or repo.repo_type == AddonManagerRepo.RepoType.PACKAGE
|
||||
):
|
||||
self.show_progress_widgets()
|
||||
self.install_worker = InstallWorkbenchWorker(repo)
|
||||
self.install_worker.status_message.connect(self.show_information)
|
||||
@@ -607,22 +685,26 @@ class CommandAddonManager:
|
||||
failed = True
|
||||
|
||||
if not failed:
|
||||
message = translate("AddonsInstaller",
|
||||
"Macro successfully installed. The macro is "
|
||||
"now available from the Macros dialog.")
|
||||
self.on_package_installed (repo, message)
|
||||
message = translate(
|
||||
"AddonsInstaller",
|
||||
"Macro successfully installed. The macro is "
|
||||
"now available from the Macros dialog.",
|
||||
)
|
||||
self.on_package_installed(repo, message)
|
||||
else:
|
||||
message = translate("AddonsInstaller", "Installation of macro failed") + ":"
|
||||
message = (
|
||||
translate("AddonsInstaller", "Installation of macro failed") + ":"
|
||||
)
|
||||
for error in errors:
|
||||
message += "\n * "
|
||||
message += error
|
||||
self.on_installation_failed (repo, message)
|
||||
self.on_installation_failed(repo, message)
|
||||
|
||||
def update(self, repo:AddonManagerRepo) -> None:
|
||||
def update(self, repo: AddonManagerRepo) -> None:
|
||||
self.install(repo)
|
||||
|
||||
def update_all(self) -> None:
|
||||
""" Asynchronously apply all available updates: individual failures are noted, but do not stop other updates """
|
||||
"""Asynchronously apply all available updates: individual failures are noted, but do not stop other updates"""
|
||||
|
||||
if hasattr(self, "update_all_worker") and self.update_all_worker:
|
||||
if self.update_all_worker.isRunning():
|
||||
@@ -630,31 +712,50 @@ class CommandAddonManager:
|
||||
|
||||
self.subupdates_succeeded = []
|
||||
self.subupdates_failed = []
|
||||
|
||||
|
||||
self.show_progress_widgets()
|
||||
self.current_progress_region = 1
|
||||
self.number_of_progress_regions = 1
|
||||
self.update_all_worker = UpdateAllWorker(self.packages_with_updates)
|
||||
self.update_all_worker.progress_made.connect(self.update_progress_bar)
|
||||
self.update_all_worker.status_message.connect(self.show_information)
|
||||
self.update_all_worker.success.connect(lambda repo : self.subupdates_succeeded.append(repo))
|
||||
self.update_all_worker.failure.connect(lambda repo : self.subupdates_failed.append(repo))
|
||||
self.update_all_worker.success.connect(
|
||||
lambda repo: self.subupdates_succeeded.append(repo)
|
||||
)
|
||||
self.update_all_worker.failure.connect(
|
||||
lambda repo: self.subupdates_failed.append(repo)
|
||||
)
|
||||
self.update_all_worker.done.connect(self.on_update_all_completed)
|
||||
self.update_all_worker.start()
|
||||
|
||||
def on_update_all_completed(self) -> None:
|
||||
self.hide_progress_widgets()
|
||||
if not self.subupdates_failed:
|
||||
message = translate ("AddonsInstaller", "All packages were successfully updated. Packages:") + "\n"
|
||||
message += ''.join([repo.name + "\n" for repo in self.subupdates_succeeded])
|
||||
message = (
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"All packages were successfully updated. Packages:",
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
message += "".join([repo.name + "\n" for repo in self.subupdates_succeeded])
|
||||
elif not self.subupdates_succeeded:
|
||||
message = translate ("AddonsInstaller", "All packages updates failed. Packages:") + "\n"
|
||||
message += ''.join([repo.name + "\n" for repo in self.subupdates_failed])
|
||||
message = (
|
||||
translate("AddonsInstaller", "All packages updates failed. Packages:")
|
||||
+ "\n"
|
||||
)
|
||||
message += "".join([repo.name + "\n" for repo in self.subupdates_failed])
|
||||
else:
|
||||
message = translate ("AddonsInstaller", "Some packages updates failed. Successful packages:") + "\n"
|
||||
message += ''.join([repo.name + "\n" for repo in self.subupdates_succeeded])
|
||||
message += translate ("AddonsInstaller", "Failed packages:") + "\n"
|
||||
message += ''.join([repo.name + "\n" for repo in self.subupdates_failed])
|
||||
message = (
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Some packages updates failed. Successful packages:",
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
message += "".join([repo.name + "\n" for repo in self.subupdates_succeeded])
|
||||
message += translate("AddonsInstaller", "Failed packages:") + "\n"
|
||||
message += "".join([repo.name + "\n" for repo in self.subupdates_failed])
|
||||
|
||||
for installed_repo in self.subupdates_succeeded:
|
||||
self.restart_required = True
|
||||
@@ -665,13 +766,15 @@ class CommandAddonManager:
|
||||
self.packages_with_updates.remove(installed_repo)
|
||||
break
|
||||
self.enable_updates(len(self.packages_with_updates))
|
||||
QtWidgets.QMessageBox.information(None,
|
||||
translate("AddonsInstaller", "Update report"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close)
|
||||
QtWidgets.QMessageBox.information(
|
||||
None,
|
||||
translate("AddonsInstaller", "Update report"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close,
|
||||
)
|
||||
|
||||
def hide_progress_widgets(self) -> None:
|
||||
""" hides the progress bar and related widgets"""
|
||||
"""hides the progress bar and related widgets"""
|
||||
|
||||
self.dialog.labelStatusInfo.hide()
|
||||
self.dialog.progressBar.hide()
|
||||
@@ -689,12 +792,14 @@ class CommandAddonManager:
|
||||
self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow)
|
||||
self.dialog.labelUpdateInProgress.show()
|
||||
|
||||
def update_progress_bar(self, current_value:int, max_value:int) -> None:
|
||||
""" Update the progress bar, showing it if it's hidden """
|
||||
def update_progress_bar(self, current_value: int, max_value: int) -> None:
|
||||
"""Update the progress bar, showing it if it's hidden"""
|
||||
|
||||
self.show_progress_widgets()
|
||||
region_size = 100 / self.number_of_progress_regions
|
||||
value = (self.current_progress_region-1)*region_size + (current_value / max_value / self.number_of_progress_regions)*region_size
|
||||
value = (self.current_progress_region - 1) * region_size + (
|
||||
current_value / max_value / self.number_of_progress_regions
|
||||
) * region_size
|
||||
self.dialog.progressBar.setValue(value)
|
||||
|
||||
def toggle_details(self) -> None:
|
||||
@@ -705,29 +810,33 @@ class CommandAddonManager:
|
||||
self.dialog.labelStatusInfo.hide()
|
||||
self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow)
|
||||
|
||||
def stop_update(self)-> None:
|
||||
def stop_update(self) -> None:
|
||||
self.cleanup_workers()
|
||||
self.hide_progress_widgets()
|
||||
|
||||
def on_package_installed(self, repo:AddonManagerRepo, message:str) -> None:
|
||||
def on_package_installed(self, repo: AddonManagerRepo, message: str) -> None:
|
||||
self.hide_progress_widgets()
|
||||
QtWidgets.QMessageBox.information(None,
|
||||
translate("AddonsInstaller", "Installation succeeded"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close)
|
||||
QtWidgets.QMessageBox.information(
|
||||
None,
|
||||
translate("AddonsInstaller", "Installation succeeded"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close,
|
||||
)
|
||||
repo.update_status = AddonManagerRepo.UpdateStatus.PENDING_RESTART
|
||||
self.item_model.reload_item(repo)
|
||||
self.packageDetails.show_repo(repo)
|
||||
self.restart_required = True
|
||||
|
||||
def on_installation_failed(self, _:AddonManagerRepo, message:str) -> None:
|
||||
def on_installation_failed(self, _: AddonManagerRepo, message: str) -> None:
|
||||
self.hide_progress_widgets()
|
||||
QtWidgets.QMessageBox.warning(None,
|
||||
translate("AddonsInstaller", "Installation failed"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close)
|
||||
QtWidgets.QMessageBox.warning(
|
||||
None,
|
||||
translate("AddonsInstaller", "Installation failed"),
|
||||
message,
|
||||
QtWidgets.QMessageBox.Close,
|
||||
)
|
||||
|
||||
def executemacro(self, repo:AddonManagerRepo) -> None:
|
||||
def executemacro(self, repo: AddonManagerRepo) -> None:
|
||||
"""executes a selected macro"""
|
||||
|
||||
macro = repo.macro
|
||||
@@ -735,7 +844,7 @@ class CommandAddonManager:
|
||||
return
|
||||
|
||||
if macro.is_installed():
|
||||
macro_path = os.path.join(self.macro_repo_dir,macro.filename)
|
||||
macro_path = os.path.join(self.macro_repo_dir, macro.filename)
|
||||
FreeCADGui.open(str(macro_path))
|
||||
self.dialog.hide()
|
||||
FreeCADGui.SendMsgToActiveView("Run")
|
||||
@@ -743,14 +852,17 @@ class CommandAddonManager:
|
||||
with tempfile.TemporaryDirectory() as dir:
|
||||
temp_install_succeeded = macro.install(dir)
|
||||
if not temp_install_succeeded:
|
||||
message = translate("AddonsInstaller", "Execution of macro failed. See console for failure details.")
|
||||
self.on_installation_failed (repo, message)
|
||||
message = translate(
|
||||
"AddonsInstaller",
|
||||
"Execution of macro failed. See console for failure details.",
|
||||
)
|
||||
self.on_installation_failed(repo, message)
|
||||
return
|
||||
else:
|
||||
macro_path = os.path.join(dir,macro.filename)
|
||||
macro_path = os.path.join(dir, macro.filename)
|
||||
FreeCADGui.open(str(macro_path))
|
||||
self.dialog.hide()
|
||||
FreeCADGui.SendMsgToActiveView("Run")
|
||||
FreeCADGui.SendMsgToActiveView("Run")
|
||||
|
||||
def remove_readonly(self, func, path, _) -> None:
|
||||
"""Remove a read-only file."""
|
||||
@@ -758,29 +870,45 @@ class CommandAddonManager:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
func(path)
|
||||
|
||||
def remove(self, repo:AddonManagerRepo) -> None:
|
||||
def remove(self, repo: AddonManagerRepo) -> None:
|
||||
"""uninstalls a macro or workbench"""
|
||||
|
||||
if repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH or \
|
||||
repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
|
||||
if (
|
||||
repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH
|
||||
or repo.repo_type == AddonManagerRepo.RepoType.PACKAGE
|
||||
):
|
||||
basedir = FreeCAD.getUserAppDataDir()
|
||||
moddir = basedir + os.sep + "Mod"
|
||||
clonedir = moddir + os.sep + repo.name
|
||||
if os.path.exists(clonedir):
|
||||
shutil.rmtree(clonedir, onerror=self.remove_readonly)
|
||||
self.item_model.update_item_status(repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED)
|
||||
self.addon_removed = True # A value to trigger the restart message on dialog close
|
||||
self.item_model.update_item_status(
|
||||
repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
)
|
||||
self.addon_removed = (
|
||||
True # A value to trigger the restart message on dialog close
|
||||
)
|
||||
self.packageDetails.show_repo(repo)
|
||||
self.restart_required = True
|
||||
else:
|
||||
self.dialog.textBrowserReadMe.setText(translate("AddonsInstaller", "Unable to remove this addon with the Addon Manager."))
|
||||
self.dialog.textBrowserReadMe.setText(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Unable to remove this addon with the Addon Manager.",
|
||||
)
|
||||
)
|
||||
|
||||
elif repo.repo_type == AddonManagerRepo.RepoType.MACRO:
|
||||
macro = repo.macro
|
||||
if macro.remove():
|
||||
self.item_model.update_item_status(repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED)
|
||||
self.item_model.update_item_status(
|
||||
repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED
|
||||
)
|
||||
self.packageDetails.show_repo(repo)
|
||||
else:
|
||||
self.dialog.textBrowserReadMe.setText(translate("AddonsInstaller", "Macro could not be removed."))
|
||||
self.dialog.textBrowserReadMe.setText(
|
||||
translate("AddonsInstaller", "Macro could not be removed.")
|
||||
)
|
||||
|
||||
|
||||
# @}
|
||||
|
||||
Reference in New Issue
Block a user