From 5d36abc13a7aa8d860e366fe3dc5664e55becc10 Mon Sep 17 00:00:00 2001 From: Sean Krueger Date: Sat, 9 Nov 2024 21:31:24 -0600 Subject: [PATCH] refactor: add function for common ProgressWidget use Extracts the create_progress_bar function from drop_import to the ProgressWidget class. Instead of creating a ProgressWidget, FunctionIterator, and CustomRunnable every time a thread-safe progress widget is needed, the from_iterable function in ProgressWidget now handles all of that. --- tagstudio/src/qt/modals/delete_unlinked.py | 27 ++-------- tagstudio/src/qt/modals/drop_import.py | 49 +++++-------------- tagstudio/src/qt/modals/fix_unlinked.py | 32 +++++------- tagstudio/src/qt/modals/merge_dupe_entries.py | 16 ++---- tagstudio/src/qt/modals/mirror_entities.py | 30 +++++------- tagstudio/src/qt/modals/relink_unlinked.py | 31 +++--------- tagstudio/src/qt/ts_qt.py | 21 +------- tagstudio/src/qt/widgets/progress.py | 30 ++++++++++-- 8 files changed, 77 insertions(+), 159 deletions(-) diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index aa14e99ad..b382d0831 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -4,7 +4,7 @@ import typing -from PySide6.QtCore import Qt, QThreadPool, Signal +from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QStandardItem, QStandardItemModel from PySide6.QtWidgets import ( QHBoxLayout, @@ -15,8 +15,6 @@ QWidget, ) from src.core.utils.missing_files import MissingRegistry -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget # Only import for type checking/autocompletion, will not be imported at runtime. @@ -82,6 +80,9 @@ def refresh_list(self): self.model.appendRow(item) def delete_entries(self): + def displayed_text(x): + return f"Deleting {x}/{self.tracker.missing_files_count} Unlinked Entries" + pw = ProgressWidget( window_title="Deleting Entries", label_text="", @@ -89,23 +90,5 @@ def delete_entries(self): minimum=0, maximum=self.tracker.missing_files_count, ) - pw.show() - - iterator = FunctionIterator(self.tracker.execute_deletion) - files_count = self.tracker.missing_files_count - iterator.value.connect( - lambda idx: ( - pw.update_progress(idx), - pw.update_label(f"Deleting {idx}/{files_count} Unlinked Entries"), - ) - ) - r = CustomRunnable(iterator.run) - QThreadPool.globalInstance().start(r) - r.done.connect( - lambda: ( - pw.hide(), - pw.deleteLater(), - self.done.emit(), - ) - ) + pw.from_iterable_function(self.tracker.execute_deletion, displayed_text, self.done.emit) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index a953fbe45..c3bdfc92e 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -4,10 +4,10 @@ import shutil from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import structlog -from PySide6.QtCore import Qt, QThreadPool, QUrl +from PySide6.QtCore import Qt, QUrl from PySide6.QtGui import QStandardItem, QStandardItemModel from PySide6.QtWidgets import ( QHBoxLayout, @@ -17,8 +17,6 @@ QVBoxLayout, QWidget, ) -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget if TYPE_CHECKING: @@ -164,12 +162,16 @@ def displayed_text(x): return text - self.create_progress_bar( + pw = ProgressWidget( + window_title="Import Files", + label_text="Importing New Files...", + cancel_button_text=None, + minimum=0, + maximum=len(self.files), + ) + + pw.from_iterable_function( self.copy_files, - "Import Files", - "Importing New Files...", - None, - len(self.files), displayed_text, self.driver.add_new_files_callback, self.deleteLater, @@ -222,32 +224,3 @@ def _get_renamed_duplicate_filename(self, filepath: Path) -> str: ) index += 1 return filepath.name - - def create_progress_bar( - self, - function: Callable, - title: str, - text: str, - cancel_button_text: str | None, - max_value: int, - update_label_callback: Callable, - *done_callback, - ): - """Displays a progress widget from an iterable function.""" - iterator = FunctionIterator(function) - - pw = ProgressWidget( - window_title=title, - label_text=text, - cancel_button_text=cancel_button_text, - minimum=0, - maximum=max_value, - ) - pw.show() - - iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) - iterator.value.connect(lambda x: pw.update_label(update_label_callback(x))) - - r = CustomRunnable(lambda: iterator.run()) - r.done.connect(lambda: (pw.hide(), [callback() for callback in done_callback])) # type: ignore - QThreadPool.globalInstance().start(r) diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 0e51144fd..999d73380 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -5,12 +5,10 @@ import typing -from PySide6.QtCore import Qt, QThreadPool +from PySide6.QtCore import Qt from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget from src.core.library import Library from src.core.utils.missing_files import MissingRegistry -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal from src.qt.modals.merge_dupe_entries import MergeDuplicateEntries from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries @@ -85,7 +83,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.delete_modal = DeleteUnlinkedEntriesModal(self.driver, self.tracker) self.delete_modal.done.connect( lambda: ( - self.set_missing_count(self.tracker.missing_files_count), + self.set_missing_count(), # refresh the grid self.driver.filter_items(), ) @@ -125,23 +123,19 @@ def refresh_missing_files(self): maximum=self.lib.entries_count, ) - pw.show() - - iterator = FunctionIterator(self.tracker.refresh_missing_files) - iterator.value.connect(lambda v: pw.update_progress(v + 1)) - r = CustomRunnable(iterator.run) - QThreadPool.globalInstance().start(r) - r.done.connect( - lambda: ( - pw.hide(), - pw.deleteLater(), - self.set_missing_count(self.tracker.missing_files_count), - self.delete_modal.refresh_list(), - ) + pw.from_iterable_function( + self.tracker.refresh_missing_files, + None, + self.set_missing_count, + self.delete_modal.refresh_list, ) - def set_missing_count(self, count: int): - self.missing_count = count + def set_missing_count(self, count: int | None = None): + if count: + self.missing_count = count + else: + self.missing_count = self.tracker.missing_files_count + if self.missing_count < 0: self.search_button.setDisabled(True) self.delete_button.setDisabled(True) diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index cc81949dd..66c94dda7 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -4,11 +4,9 @@ import typing -from PySide6.QtCore import QObject, QThreadPool, Signal +from PySide6.QtCore import QObject, Signal from src.core.library import Library from src.core.utils.dupe_files import DupeRegistry -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget # Only import for type checking/autocompletion, will not be imported at runtime. @@ -26,20 +24,12 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.tracker = DupeRegistry(library=self.lib) def merge_entries(self): - iterator = FunctionIterator(self.tracker.merge_dupe_entries) - pw = ProgressWidget( window_title="Merging Duplicate Entries", - label_text="", + label_text="Merging Duplicate Entries...", cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, ) - pw.show() - - iterator.value.connect(lambda x: pw.update_progress(x)) - iterator.value.connect(lambda: (pw.update_label("Merging Duplicate Entries..."))) - r = CustomRunnable(iterator.run) - r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit())) - QThreadPool.globalInstance().start(r) + pw.from_iterable_function(self.tracker.merge_dupe_entries, None, self.done.emit) diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index d7178ec4a..6ab199570 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -6,7 +6,7 @@ import typing from time import sleep -from PySide6.QtCore import Qt, QThreadPool, Signal +from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QStandardItem, QStandardItemModel from PySide6.QtWidgets import ( QHBoxLayout, @@ -17,8 +17,6 @@ QWidget, ) from src.core.utils.dupe_files import DupeRegistry -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget # Only import for type checking/autocompletion, will not be imported at runtime. @@ -83,28 +81,22 @@ def refresh_list(self): self.model.appendRow(QStandardItem(str(i))) def mirror_entries(self): - iterator = FunctionIterator(self.mirror_entries_runnable) + def displayed_text(x): + return f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." + pw = ProgressWidget( window_title="Mirroring Entries", - label_text=f"Mirroring 1/{self.tracker.groups_count} Entries...", + label_text="", cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, ) - pw.show() - iterator.value.connect(lambda x: pw.update_progress(x + 1)) - iterator.value.connect( - lambda x: pw.update_label(f"Mirroring {x + 1}/{self.tracker.groups_count} Entries...") - ) - r = CustomRunnable(iterator.run) - QThreadPool.globalInstance().start(r) - r.done.connect( - lambda: ( - pw.hide(), - pw.deleteLater(), - self.driver.preview_panel.update_widgets(), - self.done.emit(), - ) + + pw.from_iterable_function( + self.mirror_entries_runnable, + displayed_text, + self.driver.preview_panel.update_widgets, + self.done.emit, ) def mirror_entries_runnable(self): diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index e567f8b54..ab2a1d408 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -3,10 +3,8 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -from PySide6.QtCore import QObject, QThreadPool, Signal +from PySide6.QtCore import QObject, Signal from src.core.utils.missing_files import MissingRegistry -from src.qt.helpers.custom_runnable import CustomRunnable -from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget @@ -18,7 +16,10 @@ def __init__(self, tracker: MissingRegistry): self.tracker = tracker def repair_entries(self): - iterator = FunctionIterator(self.tracker.fix_missing_files) + def displayed_text(x): + text = f"Attempting to Relink {x}/{self.tracker.missing_files_count} Entries. \n" + text += f"{self.tracker.files_fixed_count} Successfully Relinked." + return text pw = ProgressWidget( window_title="Relinking Entries", @@ -28,24 +29,4 @@ def repair_entries(self): maximum=self.tracker.missing_files_count, ) - pw.show() - - iterator.value.connect( - lambda idx: ( - pw.update_progress(idx), - pw.update_label( - f"Attempting to Relink {idx}/{self.tracker.missing_files_count} Entries. " - f"{self.tracker.files_fixed_count} Successfully Relinked." - ), - ) - ) - - r = CustomRunnable(iterator.run) - r.done.connect( - lambda: ( - pw.hide(), - pw.deleteLater(), - self.done.emit(), - ) - ) - QThreadPool.globalInstance().start(r) + pw.from_iterable_function(self.tracker.fix_missing_files, displayed_text, self.done.emit) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 4e9ee2fa9..7d84c5006 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -702,26 +702,6 @@ def add_new_files_runnable(self, tracker: RefreshDirTracker): Threaded method. """ - # pb = QProgressDialog( - # f"Running Configured Macros on 1/{len(new_ids)} New Entries", None, 0, len(new_ids) - # ) - # pb.setFixedSize(432, 112) - # pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) - # pb.setWindowTitle('Running Macros') - # pb.setWindowModality(Qt.WindowModality.ApplicationModal) - # pb.show() - - # r = CustomRunnable(lambda: self.new_file_macros_runnable(pb, new_ids)) - # r.done.connect(lambda: (pb.hide(), pb.deleteLater(), self.filter_items(''))) - # r.run() - # # QThreadPool.globalInstance().start(r) - - # # self.main_window.statusbar.showMessage( - # # f"Running configured Macros on {len(new_ids)} new Entries...", 3 - # # ) - - # # pb.hide() - files_count = tracker.files_count iterator = FunctionIterator(tracker.save_new_files) @@ -733,6 +713,7 @@ def add_new_files_runnable(self, tracker: RefreshDirTracker): maximum=files_count, ) pw.show() + iterator.value.connect( lambda x: ( pw.update_progress(x + 1), diff --git a/tagstudio/src/qt/widgets/progress.py b/tagstudio/src/qt/widgets/progress.py index 8adfb0943..95d5906a9 100644 --- a/tagstudio/src/qt/widgets/progress.py +++ b/tagstudio/src/qt/widgets/progress.py @@ -2,11 +2,12 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio +from typing import Callable, Optional -from typing import Optional - -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QThreadPool from PySide6.QtWidgets import QProgressDialog, QVBoxLayout, QWidget +from src.qt.helpers.custom_runnable import CustomRunnable +from src.qt.helpers.function_iterator import FunctionIterator class ProgressWidget(QWidget): @@ -39,3 +40,26 @@ def update_label(self, text: str): def update_progress(self, value: int): self.pb.setValue(value) + + def _update_progress_unknown_iterable(self, value): + if hasattr(value, "__getitem__"): + self.update_progress(value[0] + 1) + else: + self.update_progress(value + 1) + + def from_iterable_function( + self, function: Callable, update_label_callback: Callable | None, *done_callbacks + ): + """Displays the progress widget from an threaded iterable function.""" + iterator = FunctionIterator(function) + iterator.value.connect(lambda x: self._update_progress_unknown_iterable(x)) + if update_label_callback: + iterator.value.connect(lambda x: self.update_label(update_label_callback(x))) + + self.show() + + r = CustomRunnable(lambda: iterator.run()) + r.done.connect( + lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks]) + ) + QThreadPool.globalInstance().start(r)