Skip to content

Commit a272b18

Browse files
feat: improve performance of "Delete Missing Entries" (#696)
* feat: Delete all unlinked entries at once (#617) This technically removes the usefulness progress indicator, but brief testing on my end had it delete ~8000 entries in less time than it took me to see if the progress indicator was properly indeterminate or not * fix(fmt): remove unused import * fix: wasn't able to delete more than 32766 entries * chose: ruff check --fix * fix: address review comment --------- Co-authored-by: Tobias Berger <toby@tobot.dev>
1 parent fce9785 commit a272b18

File tree

4 files changed

+25
-11
lines changed

4 files changed

+25
-11
lines changed

tagstudio/src/core/library/alchemy/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from src.core.query_lang import AST as Query # noqa: N811
66
from src.core.query_lang import Constraint, ConstraintType, Parser
77

8+
MAX_SQL_VARIABLES = 32766 # 32766 is the max sql bind parameter count as defined here: https://github.com/sqlite/sqlite/blob/master/src/sqliteLimit.h#L140
9+
810

911
class TagColor(enum.IntEnum):
1012
DEFAULT = 1

tagstudio/src/core/library/alchemy/library.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
)
5555
from ...enums import LibraryPrefs
5656
from .db import make_tables
57-
from .enums import FieldTypeEnum, FilterState, SortingModeEnum, TagColor
57+
from .enums import MAX_SQL_VARIABLES, FieldTypeEnum, FilterState, SortingModeEnum, TagColor
5858
from .fields import (
5959
BaseField,
6060
DatetimeField,
@@ -546,7 +546,11 @@ def add_entries(self, items: list[Entry]) -> list[int]:
546546
def remove_entries(self, entry_ids: list[int]) -> None:
547547
"""Remove Entry items matching supplied IDs from the Library."""
548548
with Session(self.engine) as session:
549-
session.query(Entry).where(Entry.id.in_(entry_ids)).delete()
549+
for sub_list in [
550+
entry_ids[i : i + MAX_SQL_VARIABLES]
551+
for i in range(0, len(entry_ids), MAX_SQL_VARIABLES)
552+
]:
553+
session.query(Entry).where(Entry.id.in_(sub_list)).delete()
550554
session.commit()
551555

552556
def has_path_entry(self, path: Path) -> bool:

tagstudio/src/core/utils/missing_files.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ def fix_missing_files(self) -> Iterator[int]:
6060
self.missing_files.remove(entry)
6161
yield i
6262

63-
def execute_deletion(self) -> Iterator[int]:
64-
for i, missing in enumerate(self.missing_files, start=1):
65-
# TODO - optimize this by removing multiple entries at once
66-
self.library.remove_entries([missing.id])
67-
yield i
63+
def execute_deletion(self) -> None:
64+
self.library.remove_entries(list(map(lambda missing: missing.id, self.missing_files)))
6865

6966
self.missing_files = []

tagstudio/src/qt/modals/delete_unlinked.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import typing
66

7-
from PySide6.QtCore import Qt, Signal
7+
from PySide6.QtCore import Qt, QThreadPool, Signal
88
from PySide6.QtGui import QStandardItem, QStandardItemModel
99
from PySide6.QtWidgets import (
1010
QHBoxLayout,
@@ -15,6 +15,7 @@
1515
QWidget,
1616
)
1717
from src.core.utils.missing_files import MissingRegistry
18+
from src.qt.helpers.custom_runnable import CustomRunnable
1819
from src.qt.translations import Translations
1920
from src.qt.widgets.progress import ProgressWidget
2021

@@ -95,8 +96,18 @@ def displayed_text(x):
9596
pw = ProgressWidget(
9697
cancel_button_text=None,
9798
minimum=0,
98-
maximum=self.tracker.missing_files_count,
99+
maximum=0,
99100
)
100101
Translations.translate_with_setter(pw.setWindowTitle, "entries.unlinked.delete.deleting")
101-
102-
pw.from_iterable_function(self.tracker.execute_deletion, displayed_text, self.done.emit)
102+
Translations.translate_with_setter(pw.update_label, "entries.unlinked.delete.deleting")
103+
pw.show()
104+
105+
r = CustomRunnable(self.tracker.execute_deletion)
106+
QThreadPool.globalInstance().start(r)
107+
r.done.connect(
108+
lambda: (
109+
pw.hide(),
110+
pw.deleteLater(),
111+
self.done.emit(),
112+
)
113+
)

0 commit comments

Comments
 (0)