Addon Manager: Refactor progress reporting
This commit is contained in:
committed by
Yorik van Havre
parent
1cf2bacf1f
commit
30177e2cf2
@@ -56,6 +56,7 @@ import addonmanager_freecad_interface as fci
|
||||
import AddonManager_rc # pylint: disable=unused-import
|
||||
from composite_view import CompositeView
|
||||
from Widgets.addonmanager_widget_global_buttons import WidgetGlobalButtonBar
|
||||
from Widgets.addonmanager_widget_progress_bar import Progress
|
||||
from package_list import PackageListItemModel
|
||||
from Addon import Addon
|
||||
from manage_python_dependencies import (
|
||||
@@ -310,7 +311,7 @@ class CommandAddonManager(QtCore.QObject):
|
||||
)
|
||||
self.button_bar.python_dependencies.clicked.connect(self.show_python_updates_dialog)
|
||||
self.button_bar.developer_tools.clicked.connect(self.show_developer_tools)
|
||||
self.composite_view.package_list.ui.progressBar.stop_clicked.connect(self.stop_update)
|
||||
self.composite_view.package_list.stop_loading.connect(self.stop_update)
|
||||
self.composite_view.package_list.setEnabled(False)
|
||||
self.composite_view.execute.connect(self.execute_macro)
|
||||
self.composite_view.install.connect(self.launch_installer_gui)
|
||||
@@ -328,9 +329,6 @@ class CommandAddonManager(QtCore.QObject):
|
||||
# begin populating the table in a set of sub-threads
|
||||
self.startup()
|
||||
|
||||
# set the label text to start with
|
||||
self.show_information(translate("AddonsInstaller", "Loading addon information"))
|
||||
|
||||
# rock 'n roll!!!
|
||||
self.dialog.exec()
|
||||
|
||||
@@ -522,9 +520,8 @@ class CommandAddonManager(QtCore.QObject):
|
||||
self.update_cache = True # Make sure to trigger the other cache updates, if the json
|
||||
# file was missing
|
||||
self.create_addon_list_worker = CreateAddonListWorker()
|
||||
self.create_addon_list_worker.status_message.connect(self.show_information)
|
||||
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
|
||||
self.update_progress_bar(10, 100)
|
||||
self.update_progress_bar(translate("AddonsInstaller", "Creating addon list"), 10, 100)
|
||||
self.create_addon_list_worker.finished.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 2
|
||||
@@ -534,7 +531,7 @@ class CommandAddonManager(QtCore.QObject):
|
||||
utils.get_cache_file_name("package_cache.json")
|
||||
)
|
||||
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
|
||||
self.update_progress_bar(10, 100)
|
||||
self.update_progress_bar(translate("AddonsInstaller", "Loading addon list"), 10, 100)
|
||||
self.create_addon_list_worker.finished.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 2
|
||||
@@ -568,9 +565,10 @@ class CommandAddonManager(QtCore.QObject):
|
||||
self.update_cache = True # Make sure to trigger the other cache updates, if the
|
||||
# json file was missing
|
||||
self.create_addon_list_worker = CreateAddonListWorker()
|
||||
self.create_addon_list_worker.status_message.connect(self.show_information)
|
||||
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
|
||||
self.update_progress_bar(10, 100)
|
||||
self.update_progress_bar(
|
||||
translate("AddonsInstaller", "Creating macro list"), 10, 100
|
||||
)
|
||||
self.create_addon_list_worker.finished.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 2
|
||||
@@ -608,7 +606,6 @@ class CommandAddonManager(QtCore.QObject):
|
||||
def update_metadata_cache(self) -> None:
|
||||
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.finished.connect(
|
||||
self.do_next_startup_phase
|
||||
) # Link to step 4
|
||||
@@ -644,7 +641,6 @@ class CommandAddonManager(QtCore.QObject):
|
||||
def load_macro_metadata(self) -> None:
|
||||
if self.update_cache:
|
||||
self.load_macro_metadata_worker = CacheMacroCodeWorker(self.item_model.repos)
|
||||
self.load_macro_metadata_worker.status_message.connect(self.show_information)
|
||||
self.load_macro_metadata_worker.update_macro.connect(self.on_package_updated)
|
||||
self.load_macro_metadata_worker.progress_made.connect(self.update_progress_bar)
|
||||
self.load_macro_metadata_worker.finished.connect(self.do_next_startup_phase)
|
||||
@@ -798,12 +794,6 @@ class CommandAddonManager(QtCore.QObject):
|
||||
return
|
||||
self.item_model.append_item(addon_repo)
|
||||
|
||||
def show_information(self, message: str) -> None:
|
||||
"""shows generic text in the information pane"""
|
||||
|
||||
self.composite_view.package_list.ui.progressBar.set_status(message)
|
||||
self.composite_view.package_list.ui.progressBar.repaint()
|
||||
|
||||
def append_to_repos_list(self, repo: Addon) -> None:
|
||||
"""this function allows threads to update the main list of workbenches"""
|
||||
self.item_model.append_item(repo)
|
||||
@@ -862,15 +852,12 @@ class CommandAddonManager(QtCore.QObject):
|
||||
|
||||
def hide_progress_widgets(self) -> None:
|
||||
"""hides the progress bar and related widgets"""
|
||||
|
||||
self.composite_view.package_list.ui.progressBar.hide()
|
||||
self.composite_view.package_list.ui.view_bar.search.setFocus()
|
||||
self.composite_view.package_list.set_loading(False)
|
||||
|
||||
def show_progress_widgets(self) -> None:
|
||||
if self.composite_view.package_list.ui.progressBar.isHidden():
|
||||
self.composite_view.package_list.ui.progressBar.show()
|
||||
self.composite_view.package_list.set_loading(True)
|
||||
|
||||
def update_progress_bar(self, current_value: int, max_value: int) -> None:
|
||||
def update_progress_bar(self, message: str, current_value: int, max_value: int) -> None:
|
||||
"""Update the progress bar, showing it if it's hidden"""
|
||||
|
||||
max_value = max_value if max_value > 0 else 1
|
||||
@@ -881,14 +868,14 @@ class CommandAddonManager(QtCore.QObject):
|
||||
current_value = max_value
|
||||
|
||||
self.show_progress_widgets()
|
||||
region_size = 100.0 / self.number_of_progress_regions
|
||||
completed_region_portion = (self.current_progress_region - 1) * region_size
|
||||
current_region_portion = (float(current_value) / float(max_value)) * region_size
|
||||
value = completed_region_portion + current_region_portion
|
||||
self.composite_view.package_list.ui.progressBar.set_value(
|
||||
value * 10
|
||||
) # Out of 1000 segments, so it moves sort of smoothly
|
||||
self.composite_view.package_list.ui.progressBar.repaint()
|
||||
|
||||
progress = Progress(
|
||||
status_text=message,
|
||||
number_of_tasks=self.number_of_progress_regions,
|
||||
current_task=self.current_progress_region - 1,
|
||||
current_task_progress=current_value / max_value,
|
||||
)
|
||||
self.composite_view.package_list.update_loading_progress(progress)
|
||||
|
||||
def stop_update(self) -> None:
|
||||
self.cleanup_workers()
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2022 FreeCAD Project Association *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.append("../..")
|
||||
|
||||
from Widgets.addonmanager_widget_progress_bar import Progress
|
||||
|
||||
|
||||
class TestProgress(unittest.TestCase):
|
||||
|
||||
def test_default_construction(self):
|
||||
"""Given no parameters, a single-task Progress object is initialized with zero progress"""
|
||||
progress = Progress()
|
||||
self.assertEqual(progress.status_text, "")
|
||||
self.assertEqual(progress.number_of_tasks, 1)
|
||||
self.assertEqual(progress.current_task, 0)
|
||||
self.assertEqual(progress.current_task_progress, 0.0)
|
||||
|
||||
def test_good_parameters(self):
|
||||
"""Given good parameters, no exception is raised"""
|
||||
_ = Progress(
|
||||
status_text="Some text", number_of_tasks=1, current_task=0, current_task_progress=0.0
|
||||
)
|
||||
|
||||
def test_zero_task_count(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Progress(number_of_tasks=0)
|
||||
|
||||
def test_negative_task_count(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Progress(number_of_tasks=-1)
|
||||
|
||||
def test_setting_status_post_creation(self):
|
||||
progress = Progress()
|
||||
self.assertEqual(progress.status_text, "")
|
||||
progress.status_text = "Some status"
|
||||
self.assertEqual(progress.status_text, "Some status")
|
||||
|
||||
def test_setting_task_count(self):
|
||||
progress = Progress()
|
||||
progress.number_of_tasks = 10
|
||||
self.assertEqual(progress.number_of_tasks, 10)
|
||||
|
||||
def test_setting_negative_task_count(self):
|
||||
progress = Progress()
|
||||
with self.assertRaises(ValueError):
|
||||
progress.number_of_tasks = -1
|
||||
|
||||
def test_setting_invalid_task_count(self):
|
||||
progress = Progress()
|
||||
with self.assertRaises(TypeError):
|
||||
progress.number_of_tasks = 3.14159
|
||||
|
||||
def test_setting_current_task(self):
|
||||
progress = Progress(number_of_tasks=10)
|
||||
progress.number_of_tasks = 5
|
||||
self.assertEqual(progress.number_of_tasks, 5)
|
||||
|
||||
def test_setting_current_task_greater_than_task_count(self):
|
||||
progress = Progress()
|
||||
progress.number_of_tasks = 10
|
||||
with self.assertRaises(ValueError):
|
||||
progress.current_task = 11
|
||||
|
||||
def test_setting_current_task_equal_to_task_count(self):
|
||||
"""current_task is zero-indexed, so this is too high"""
|
||||
progress = Progress()
|
||||
progress.number_of_tasks = 10
|
||||
with self.assertRaises(ValueError):
|
||||
progress.current_task = 10
|
||||
|
||||
def test_setting_current_task_negative(self):
|
||||
progress = Progress()
|
||||
with self.assertRaises(ValueError):
|
||||
progress.current_task = -1
|
||||
|
||||
def test_setting_current_task_invalid(self):
|
||||
progress = Progress()
|
||||
with self.assertRaises(TypeError):
|
||||
progress.current_task = 2.718281
|
||||
|
||||
def test_setting_current_task_progress(self):
|
||||
progress = Progress()
|
||||
progress.current_task_progress = 50.0
|
||||
self.assertEqual(progress.current_task_progress, 50.0)
|
||||
|
||||
def test_setting_current_task_progress_too_low(self):
|
||||
progress = Progress()
|
||||
progress.current_task_progress = -0.01
|
||||
self.assertEqual(progress.current_task_progress, 0.0)
|
||||
|
||||
def test_setting_current_task_progress_too_high(self):
|
||||
progress = Progress()
|
||||
progress.current_task_progress = 100.001
|
||||
self.assertEqual(progress.current_task_progress, 100.0)
|
||||
|
||||
def test_incrementing_task(self):
|
||||
progress = Progress(number_of_tasks=10, current_task_progress=100.0)
|
||||
progress.next_task()
|
||||
self.assertEqual(progress.current_task, 1)
|
||||
self.assertEqual(progress.current_task_progress, 0.0)
|
||||
|
||||
def test_incrementing_task_too_high(self):
|
||||
progress = Progress(number_of_tasks=10, current_task=9, current_task_progress=100.0)
|
||||
with self.assertRaises(ValueError):
|
||||
progress.next_task()
|
||||
|
||||
def test_overall_progress_simple(self):
|
||||
progress = Progress()
|
||||
self.assertEqual(progress.overall_progress(), 0.0)
|
||||
|
||||
def test_overall_progress_with_ranges(self):
|
||||
progress = Progress(number_of_tasks=2, current_task=1, current_task_progress=0.0)
|
||||
self.assertAlmostEqual(progress.overall_progress(), 0.5)
|
||||
|
||||
def test_overall_progress_with_ranges_and_progress(self):
|
||||
progress = Progress(number_of_tasks=10, current_task=5, current_task_progress=50.0)
|
||||
self.assertAlmostEqual(progress.overall_progress(), 0.55)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -36,27 +36,103 @@ except ImportError:
|
||||
|
||||
# Get whatever version of PySide we can
|
||||
try:
|
||||
import PySide # Use the FreeCAD wrapper
|
||||
from PySide import QtCore, QtGui, QtWidgets # Use the FreeCAD wrapper
|
||||
except ImportError:
|
||||
try:
|
||||
import PySide6 # Outside FreeCAD, try Qt6 first
|
||||
|
||||
PySide = PySide6
|
||||
from PySide6 import QtCore, QtGui, QtWidgets # Outside FreeCAD, try Qt6 first
|
||||
except ImportError:
|
||||
import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import)
|
||||
from PySide2 import QtCore, QtGui, QtWidgets # Fall back to Qt5
|
||||
|
||||
PySide = PySide2
|
||||
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from dataclasses import dataclass
|
||||
|
||||
_TOTAL_INCREMENTS = 1000
|
||||
|
||||
|
||||
class Progress:
|
||||
"""Represents progress through a process composed of multiple sub-tasks."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
status_text: str = "",
|
||||
number_of_tasks: int = 1,
|
||||
current_task: int = 0,
|
||||
current_task_progress: float = 0.0,
|
||||
):
|
||||
if number_of_tasks < 1:
|
||||
raise ValueError(f"Number of tasks must be at least one, not {number_of_tasks}")
|
||||
if current_task < 0 or current_task >= number_of_tasks:
|
||||
raise ValueError(
|
||||
"Current task must be between 0 and the number of tasks "
|
||||
f"({number_of_tasks}), not {current_task}"
|
||||
)
|
||||
if current_task_progress < 0.0:
|
||||
current_task_progress = 0.0
|
||||
elif current_task_progress > 100.0:
|
||||
current_task_progress = 100.0
|
||||
self.status_text: str = status_text
|
||||
self._number_of_tasks: int = number_of_tasks
|
||||
self._current_task: int = current_task
|
||||
self._current_task_progress: float = current_task_progress
|
||||
|
||||
@property
|
||||
def number_of_tasks(self):
|
||||
return self._number_of_tasks
|
||||
|
||||
@number_of_tasks.setter
|
||||
def number_of_tasks(self, value: int):
|
||||
if not isinstance(value, int):
|
||||
raise TypeError("Number of tasks must be an integer")
|
||||
if value < 1:
|
||||
raise ValueError("Number of tasks must be at least one")
|
||||
self._number_of_tasks = value
|
||||
|
||||
@property
|
||||
def current_task(self):
|
||||
"""The current task (zero-indexed, always less than the number of tasks)"""
|
||||
return self._current_task
|
||||
|
||||
@current_task.setter
|
||||
def current_task(self, value: int):
|
||||
if not isinstance(value, int):
|
||||
raise TypeError("Current task must be an integer")
|
||||
if value < 0:
|
||||
raise ValueError("Current task must be at least zero")
|
||||
if value >= self._number_of_tasks:
|
||||
raise ValueError("Current task must be less than the total number of tasks")
|
||||
self._current_task = value
|
||||
|
||||
@property
|
||||
def current_task_progress(self):
|
||||
"""Current task progress, guaranteed to be in the range [0.0, 100.0]. Attempts to set a
|
||||
value outside that range are clamped to the range."""
|
||||
return self._current_task_progress
|
||||
|
||||
@current_task_progress.setter
|
||||
def current_task_progress(self, value: float):
|
||||
"""Set the current task's progress. Rather than raising an exception when the value is
|
||||
outside the expected range of [0,100], clamp the task progress to allow for some
|
||||
floating point imprecision in its calculation."""
|
||||
if value < 0.0:
|
||||
value = 0.0
|
||||
elif value > 100.0:
|
||||
value = 100.0
|
||||
self._current_task_progress = value
|
||||
|
||||
def next_task(self) -> None:
|
||||
"""Increment the task counter and reset the progress"""
|
||||
self.current_task += 1
|
||||
self.current_task_progress = 0.0
|
||||
|
||||
def overall_progress(self) -> float:
|
||||
"""Gets the overall progress as a fractional value in the range [0, 1]"""
|
||||
base = self._current_task / self._number_of_tasks
|
||||
fraction = self._current_task_progress / (100.0 * self._number_of_tasks)
|
||||
return base + fraction
|
||||
|
||||
|
||||
class WidgetProgressBar(QtWidgets.QWidget):
|
||||
"""A multipart progress bar widget, including a stop button and a status label. Defaults to a
|
||||
single range with 100 increments, but can be configured with any number of major and minor
|
||||
ranges. Clicking the stop button will emit a signal, but does not otherwise affect the
|
||||
widget."""
|
||||
"""A multipart progress bar widget, including a stop button and a status label."""
|
||||
|
||||
stop_clicked = QtCore.Signal()
|
||||
|
||||
@@ -87,8 +163,6 @@ class WidgetProgressBar(QtWidgets.QWidget):
|
||||
self.vertical_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
def set_status(self, status: str):
|
||||
self.status_label.setText(status)
|
||||
|
||||
def set_value(self, value: int):
|
||||
self.progress_bar.setValue(value)
|
||||
def set_progress(self, progress: Progress) -> None:
|
||||
self.status_label.setText(progress.status_text)
|
||||
self.progress_bar.setValue(progress.overall_progress() * _TOTAL_INCREMENTS)
|
||||
|
||||
@@ -53,8 +53,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
"""Scan through all available packages and see if our local copy of package.xml needs to be
|
||||
updated"""
|
||||
|
||||
status_message = QtCore.Signal(str)
|
||||
progress_made = QtCore.Signal(int, int)
|
||||
progress_made = QtCore.Signal(str, int, int)
|
||||
package_updated = QtCore.Signal(Addon)
|
||||
|
||||
class RequestType(Enum):
|
||||
@@ -166,18 +165,26 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
"""Callback for handling a completed metadata file download."""
|
||||
if index in self.requests:
|
||||
self.requests_completed += 1
|
||||
self.progress_made.emit(self.requests_completed, self.total_requests)
|
||||
request = self.requests.pop(index)
|
||||
if code == 200: # HTTP success
|
||||
self.updated_repos.add(request[0]) # mark this repo as updated
|
||||
file = "unknown"
|
||||
if request[1] == UpdateMetadataCacheWorker.RequestType.PACKAGE_XML:
|
||||
self.process_package_xml(request[0], data)
|
||||
file = "package.xml"
|
||||
elif request[1] == UpdateMetadataCacheWorker.RequestType.METADATA_TXT:
|
||||
self.process_metadata_txt(request[0], data)
|
||||
file = "metadata.txt"
|
||||
elif request[1] == UpdateMetadataCacheWorker.RequestType.REQUIREMENTS_TXT:
|
||||
self.process_requirements_txt(request[0], data)
|
||||
file = "requirements.txt"
|
||||
elif request[1] == UpdateMetadataCacheWorker.RequestType.ICON:
|
||||
self.process_icon(request[0], data)
|
||||
file = "icon"
|
||||
message = translate("AddonsInstaller", "Downloaded {} for {}").format(
|
||||
file, request[0].display_name
|
||||
)
|
||||
self.progress_made.emit(message, self.requests_completed, self.total_requests)
|
||||
|
||||
def process_package_xml(self, repo: Addon, data: QtCore.QByteArray):
|
||||
"""Process the package.xml metadata file"""
|
||||
@@ -197,9 +204,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
return
|
||||
repo.set_metadata(metadata)
|
||||
FreeCAD.Console.PrintLog(f"Downloaded package.xml for {repo.name}\n")
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Downloaded package.xml for {}").format(repo.name)
|
||||
)
|
||||
|
||||
# Grab a new copy of the icon as well: we couldn't enqueue this earlier because
|
||||
# we didn't know the path to it, which is stored in the package.xml file.
|
||||
@@ -251,10 +255,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
|
||||
def process_metadata_txt(self, repo: Addon, data: QtCore.QByteArray):
|
||||
"""Process the metadata.txt metadata file"""
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Downloaded metadata.txt for {}").format(repo.display_name)
|
||||
)
|
||||
|
||||
f = self._ensure_string(data, repo.name, "metadata.txt")
|
||||
lines = f.splitlines()
|
||||
for line in lines:
|
||||
@@ -291,12 +291,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
|
||||
def process_requirements_txt(self, repo: Addon, data: QtCore.QByteArray):
|
||||
"""Process the requirements.txt metadata file"""
|
||||
self.status_message.emit(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Downloaded requirements.txt for {}",
|
||||
).format(repo.display_name)
|
||||
)
|
||||
|
||||
f = self._ensure_string(data, repo.name, "requirements.txt")
|
||||
lines = f.splitlines()
|
||||
@@ -312,9 +306,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
|
||||
|
||||
def process_icon(self, repo: Addon, data: QtCore.QByteArray):
|
||||
"""Convert icon data into a valid icon file and store it"""
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Downloaded icon for {}").format(repo.display_name)
|
||||
)
|
||||
cache_file = repo.get_cached_icon_filename()
|
||||
with open(cache_file, "wb") as icon_file:
|
||||
icon_file.write(data.data())
|
||||
|
||||
@@ -57,8 +57,8 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
"""This worker updates the list of available workbenches, emitting an "addon_repo"
|
||||
signal for each Addon as they are processed."""
|
||||
|
||||
status_message = QtCore.Signal(str)
|
||||
addon_repo = QtCore.Signal(object)
|
||||
progress_made = QtCore.Signal(str, int, int)
|
||||
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
@@ -121,7 +121,6 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
"Failed to connect to GitHub. Check your connection and proxy settings.",
|
||||
)
|
||||
FreeCAD.Console.PrintError(message + "\n")
|
||||
self.status_message.emit(message)
|
||||
raise ConnectionError
|
||||
|
||||
def _process_deprecated(self, deprecated_addons):
|
||||
@@ -265,8 +264,6 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
repo.obsolete = True
|
||||
self.addon_repo.emit(repo)
|
||||
|
||||
self.status_message.emit(translate("AddonsInstaller", "Workbenches list was updated."))
|
||||
|
||||
def _retrieve_macros_from_git(self):
|
||||
"""Retrieve macros from FreeCAD-macros.git
|
||||
|
||||
@@ -281,7 +278,6 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
"AddonsInstaller",
|
||||
"Git is disabled, skipping Git macros",
|
||||
)
|
||||
self.status_message.emit(message)
|
||||
FreeCAD.Console.PrintWarning(message + "\n")
|
||||
return
|
||||
|
||||
@@ -529,7 +525,7 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
"""This worker checks for available updates for all workbenches"""
|
||||
|
||||
update_status = QtCore.Signal(Addon)
|
||||
progress_made = QtCore.Signal(int, int)
|
||||
progress_made = QtCore.Signal(str, int, int)
|
||||
|
||||
def __init__(self, repos: List[Addon]):
|
||||
|
||||
@@ -549,7 +545,10 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
for repo in self.repos:
|
||||
if self.current_thread.isInterruptionRequested():
|
||||
return
|
||||
self.progress_made.emit(count, len(self.repos))
|
||||
message = translate("AddonsInstaller", "Checking {} for update").format(
|
||||
repo.display_name
|
||||
)
|
||||
self.progress_made.emit(message, count, len(self.repos))
|
||||
count += 1
|
||||
if repo.status() == Addon.Status.UNCHECKED:
|
||||
if repo.repo_type == Addon.Kind.WORKBENCH:
|
||||
@@ -743,9 +742,8 @@ class UpdateChecker:
|
||||
class CacheMacroCodeWorker(QtCore.QThread):
|
||||
"""Download and cache the macro code, and parse its internal metadata"""
|
||||
|
||||
status_message = QtCore.Signal(str)
|
||||
update_macro = QtCore.Signal(Addon)
|
||||
progress_made = QtCore.Signal(int, int)
|
||||
progress_made = QtCore.Signal(str, int, int)
|
||||
|
||||
def __init__(self, repos: List[Addon]) -> None:
|
||||
QtCore.QThread.__init__(self)
|
||||
@@ -761,8 +759,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
self.status_message.emit(translate("AddonsInstaller", "Caching macro code..."))
|
||||
|
||||
self.repo_queue = queue.Queue()
|
||||
num_macros = 0
|
||||
for repo in self.repos:
|
||||
@@ -846,7 +842,8 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
|
||||
self.progress_made.emit(len(self.repos) - self.repo_queue.qsize(), len(self.repos))
|
||||
message = translate("AddonsInstaller", "Caching {} macro").format(repo.display_name)
|
||||
self.progress_made.emit(message, len(self.repos) - self.repo_queue.qsize(), len(self.repos))
|
||||
|
||||
try:
|
||||
next_repo = self.repo_queue.get_nowait()
|
||||
@@ -857,12 +854,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
self.terminators.append(
|
||||
QtCore.QTimer.singleShot(10000, lambda: self.terminate(worker))
|
||||
)
|
||||
self.status_message.emit(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Getting metadata from macro {}",
|
||||
).format(next_repo.macro.name)
|
||||
)
|
||||
worker.start()
|
||||
except queue.Empty:
|
||||
pass
|
||||
@@ -895,7 +886,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
class GetMacroDetailsWorker(QtCore.QThread):
|
||||
"""Retrieve the macro details for a macro"""
|
||||
|
||||
status_message = QtCore.Signal(str)
|
||||
readme_updated = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, repo):
|
||||
@@ -907,12 +897,9 @@ class GetMacroDetailsWorker(QtCore.QThread):
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
self.status_message.emit(translate("AddonsInstaller", "Retrieving macro description..."))
|
||||
if not self.macro.parsed and self.macro.on_git:
|
||||
self.status_message.emit(translate("AddonsInstaller", "Retrieving info from Git"))
|
||||
self.macro.fill_details_from_file(self.macro.src_filename)
|
||||
if not self.macro.parsed and self.macro.on_wiki:
|
||||
self.status_message.emit(translate("AddonsInstaller", "Retrieving info from wiki"))
|
||||
mac = self.macro.name.replace(" ", "_")
|
||||
mac = mac.replace("&", "%26")
|
||||
mac = mac.replace("+", "%2B")
|
||||
|
||||
@@ -39,7 +39,7 @@ from addonmanager_metadata import get_first_supported_freecad_version, Version
|
||||
from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar, SortOptions
|
||||
from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle
|
||||
from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter
|
||||
from Widgets.addonmanager_widget_progress_bar import WidgetProgressBar
|
||||
from Widgets.addonmanager_widget_progress_bar import Progress, WidgetProgressBar
|
||||
from addonmanager_licenses import get_license_manager
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
@@ -50,9 +50,11 @@ translate = FreeCAD.Qt.translate
|
||||
|
||||
class PackageList(QtWidgets.QWidget):
|
||||
"""A widget that shows a list of packages and various widgets to control the
|
||||
display of the list"""
|
||||
display of the list, including a progress bar that can display and interrupt the load
|
||||
process."""
|
||||
|
||||
itemSelected = QtCore.Signal(Addon)
|
||||
stop_loading = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -70,6 +72,7 @@ class PackageList(QtWidgets.QWidget):
|
||||
self.ui.view_bar.sort_changed.connect(self.item_filter.setSortRole)
|
||||
self.ui.view_bar.sort_changed.connect(self.item_delegate.set_sort)
|
||||
self.ui.view_bar.sort_order_changed.connect(lambda order: self.item_filter.sort(0, order))
|
||||
self.ui.progress_bar.stop_clicked.connect(self.stop_loading)
|
||||
|
||||
# Set up the view the same as the last time:
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
@@ -157,6 +160,19 @@ class PackageList(QtWidgets.QWidget):
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
pref.SetInt("ViewStyle", style)
|
||||
|
||||
def set_loading(self, is_loading: bool) -> None:
|
||||
"""Set the loading status of this package list: when a package list is loading, it shows
|
||||
a progress bar. When it is no longer loading, the bar is hidden and the search bar gets
|
||||
the focus."""
|
||||
if is_loading:
|
||||
self.ui.progress_bar.show()
|
||||
else:
|
||||
self.ui.progress_bar.hide()
|
||||
self.ui.view_bar.search.setFocus()
|
||||
|
||||
def update_loading_progress(self, progress: Progress) -> None:
|
||||
self.ui.progress_bar.set_progress(progress)
|
||||
|
||||
|
||||
class PackageListItemModel(QtCore.QAbstractListModel):
|
||||
"""The model for use with the PackageList class."""
|
||||
@@ -764,7 +780,7 @@ class Ui_PackageList:
|
||||
|
||||
self.verticalLayout.addWidget(self.listPackages)
|
||||
|
||||
self.progressBar = WidgetProgressBar()
|
||||
self.verticalLayout.addWidget(self.progressBar)
|
||||
self.progress_bar = WidgetProgressBar()
|
||||
self.verticalLayout.addWidget(self.progress_bar)
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(form)
|
||||
|
||||
Reference in New Issue
Block a user