From a865573c4b70d6c2c0889e07c85bff2a879a5e5c Mon Sep 17 00:00:00 2001 From: Ludvig Sandh <55193593+ludvig-sandh@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:53:14 +0100 Subject: [PATCH] fix: fix off-by-one issues in progress bars (#1293) * fix: faulty progress bar The progress bar UI element always showed 1 step ahead of what it supposed to, also causing the last step to display a visual glitch (empty window) * fix: corrected drop import progress bar text * fix: corrected relink progress bar Update progress bar at the beginning of the relink iteration instead of the end, which previously showed incorrect progress text and an empty window during the first iteration. * fix: corrected 'refresh ignore entries' progress bar Update progress bar at the beginning of the refresh loop iteration instead of the end, which previously showed incorrect progress text and an empty window during the first iteration. * fix: corrected 'refresh unlinked' progress bar Update progress bar at the beginning of the refresh loop iteration instead of the end, which previously showed incorrect progress text and an empty window during the first iteration. * fix: corrected 'mirror entries' progress bar Same as last commit * fix: Unknown key in 'mirror entries' progress bar text (translation formatting) * fix: corrected 'merge duplicates' progress bar Note: Seems like this bar is never used in the UI. But if it is in the future, this fixes it just like the other progress bars. --- .../library/alchemy/registries/dupe_files_registry.py | 2 +- .../core/library/alchemy/registries/ignored_registry.py | 2 +- .../core/library/alchemy/registries/unlinked_registry.py | 4 ++-- src/tagstudio/qt/mixed/drop_import_modal.py | 6 +++--- src/tagstudio/qt/mixed/mirror_entries_modal.py | 4 ++-- src/tagstudio/qt/mixed/progress_bar.py | 9 ++++++--- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/tagstudio/core/library/alchemy/registries/dupe_files_registry.py b/src/tagstudio/core/library/alchemy/registries/dupe_files_registry.py index 01147920..01da14a9 100644 --- a/src/tagstudio/core/library/alchemy/registries/dupe_files_registry.py +++ b/src/tagstudio/core/library/alchemy/registries/dupe_files_registry.py @@ -80,7 +80,7 @@ class DupeFilesRegistry: ) for i, entries in enumerate(self.groups): + yield i remove_ids = entries[1:] logger.info("Removing entries group", ids=remove_ids) self.library.remove_entries([e.id for e in remove_ids]) - yield i - 1 # The -1 waits for the next step to finish diff --git a/src/tagstudio/core/library/alchemy/registries/ignored_registry.py b/src/tagstudio/core/library/alchemy/registries/ignored_registry.py index 0748875a..5afa6636 100644 --- a/src/tagstudio/core/library/alchemy/registries/ignored_registry.py +++ b/src/tagstudio/core/library/alchemy/registries/ignored_registry.py @@ -35,12 +35,12 @@ class IgnoredRegistry: self.ignored_entries = [] for i, entry in enumerate(self.lib.all_entries()): + yield i if not Ignore.compiled_patterns: # If the compiled_patterns has malfunctioned, don't consider that a false positive yield i elif Ignore.compiled_patterns.match(entry.path): self.ignored_entries.append(entry) - yield i def remove_ignored_entries(self) -> None: self.lib.remove_entries(list(map(lambda ignored: ignored.id, self.ignored_entries))) diff --git a/src/tagstudio/core/library/alchemy/registries/unlinked_registry.py b/src/tagstudio/core/library/alchemy/registries/unlinked_registry.py index 3f2e8539..0e876ac5 100644 --- a/src/tagstudio/core/library/alchemy/registries/unlinked_registry.py +++ b/src/tagstudio/core/library/alchemy/registries/unlinked_registry.py @@ -38,10 +38,10 @@ class UnlinkedRegistry: self.unlinked_entries = [] for i, entry in enumerate(self.lib.all_entries()): + yield i full_path = unwrap(self.lib.library_dir) / entry.path if not full_path.exists() or not full_path.is_file(): self.unlinked_entries.append(entry) - yield i def match_unlinked_file_entry(self, match_entry: Entry) -> list[Path]: """Try and match unlinked file entries with matching results in the library directory. @@ -72,6 +72,7 @@ class UnlinkedRegistry: self.files_fixed_count = 0 matched_entries: list[Entry] = [] for i, entry in enumerate(self.unlinked_entries): + yield i item_matches = self.match_unlinked_file_entry(entry) if len(item_matches) == 1: logger.info( @@ -88,7 +89,6 @@ class UnlinkedRegistry: continue self.files_fixed_count += 1 matched_entries.append(entry) - yield i for entry in matched_entries: self.unlinked_entries.remove(entry) diff --git a/src/tagstudio/qt/mixed/drop_import_modal.py b/src/tagstudio/qt/mixed/drop_import_modal.py index 570448b5..415302bb 100644 --- a/src/tagstudio/qt/mixed/drop_import_modal.py +++ b/src/tagstudio/qt/mixed/drop_import_modal.py @@ -167,9 +167,9 @@ class DropImportModal(QWidget): def displayed_text(x): return Translations.format( "drop_import.progress.label.singular" - if x[0] + 1 == 1 + if x[0] == 1 else "drop_import.progress.label.plural", - count=x[0] + 1, + count=x[0], suffix=f" {x[1]} {self.choice.value}" if self.choice else "", ) @@ -193,6 +193,7 @@ class DropImportModal(QWidget): file_count = 0 duplicated_files_progress = 0 for file in self.files: + yield [file_count, duplicated_files_progress] if file.is_dir(): continue @@ -213,7 +214,6 @@ class DropImportModal(QWidget): shutil.copyfile(file, unwrap(self.driver.lib.library_dir) / dest_file) file_count += 1 - yield [file_count, duplicated_files_progress] def _get_relative_path(self, path: Path) -> Path: for dir in self.dirs_in_root: diff --git a/src/tagstudio/qt/mixed/mirror_entries_modal.py b/src/tagstudio/qt/mixed/mirror_entries_modal.py index d06627a6..07714088 100644 --- a/src/tagstudio/qt/mixed/mirror_entries_modal.py +++ b/src/tagstudio/qt/mixed/mirror_entries_modal.py @@ -73,7 +73,7 @@ class MirrorEntriesModal(QWidget): def mirror_entries(self): def displayed_text(x): return Translations.format( - "entries.mirror.label", idx=x + 1, count=self.tracker.groups_count + "entries.mirror.label", idx=x, total=self.tracker.groups_count ) pw = ProgressWidget( @@ -94,9 +94,9 @@ class MirrorEntriesModal(QWidget): mirrored: list = [] lib = self.driver.lib for i, entries in enumerate(self.tracker.groups): + yield i lib.mirror_entry_fields(entries) sleep(0.005) - yield i for d in mirrored: self.tracker.groups.remove(d) diff --git a/src/tagstudio/qt/mixed/progress_bar.py b/src/tagstudio/qt/mixed/progress_bar.py index ef1bb66a..cc413c5d 100644 --- a/src/tagstudio/qt/mixed/progress_bar.py +++ b/src/tagstudio/qt/mixed/progress_bar.py @@ -45,14 +45,17 @@ class ProgressWidget(QWidget): def _update_progress_unknown_iterable(self, value): if hasattr(value, "__getitem__"): - self.update_progress(value[0] + 1) + self.update_progress(value[0]) else: - self.update_progress(value + 1) + self.update_progress(value) def from_iterable_function( self, function: Callable, update_label_callback: Callable | None, *done_callbacks ): - """Display the progress widget from a threaded iterable function.""" + """Display the progress widget from a threaded iterable function. + + Method expects the iterable to yield the number of completed objects. + """ iterator = FunctionIterator(function) iterator.value.connect(lambda x: self._update_progress_unknown_iterable(x)) if update_label_callback: