From 1918031399562b93c61cb534a69cb97e428eea44 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 8 May 2021 16:56:51 +1000 Subject: [PATCH] update find_duplicates to use QueryOp/CollectionOp --- pylib/anki/collection.py | 2 +- qt/aqt/browser/find_duplicates.py | 91 ++++++++++++++----------------- qt/aqt/main.py | 2 + qt/aqt/taskman.py | 7 ++- 4 files changed, 48 insertions(+), 54 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index fcae9dc79..cc54c9ea1 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -608,7 +608,7 @@ class Collection: return self._backend.field_names_for_notes(nids) # returns array of ("dupestr", [nids]) - def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]: + def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[str, list]]: nids = self.find_notes( self.build_search_string(search, SearchNode(field_name=fieldName)) ) diff --git a/qt/aqt/browser/find_duplicates.py b/qt/aqt/browser/find_duplicates.py index 283c06a7a..9703a9ce7 100644 --- a/qt/aqt/browser/find_duplicates.py +++ b/qt/aqt/browser/find_duplicates.py @@ -4,15 +4,17 @@ from __future__ import annotations import html -from typing import Any, List, Optional +from typing import Any, List, Optional, Tuple import anki import anki.find import aqt from anki.collection import SearchNode +from anki.notes import NoteId from aqt.qt import * -from ..main import ResetReason +from ..operations import QueryOp +from ..operations.tag import add_tags_to_notes from ..utils import ( disable_help_button, restore_combo_history, @@ -21,11 +23,8 @@ from ..utils import ( save_combo_history, save_combo_index_for_session, saveGeom, - showWarning, - tooltip, tr, ) -from ..webview import AnkiWebView from . import Browser @@ -35,7 +34,7 @@ class FindDuplicatesDialog(QDialog): self.browser = browser self.mw = mw self.mw.garbage_collect_on_dialog_finish(self) - form = aqt.forms.finddupes.Ui_Dialog() + self.form = form = aqt.forms.finddupes.Ui_Dialog() form.setupUi(self) restoreGeom(self, "findDupes") disable_help_button(self) @@ -50,56 +49,47 @@ class FindDuplicatesDialog(QDialog): # links form.webView.set_title("find duplicates") - form.webView.set_bridge_command(self.dupeLinkClicked, context=self) + form.webView.set_bridge_command(self._on_duplicate_clicked, context=self) form.webView.stdHtml("", context=self) - def onFin(code: Any) -> None: + def on_finished(code: Any) -> None: saveGeom(self, "findDupes") - qconnect(self.finished, onFin) + qconnect(self.finished, on_finished) - def onClick() -> None: + def on_click() -> None: search_text = save_combo_history( form.search, searchHistory, "findDupesFind" ) save_combo_index_for_session(form.fields, "findDupesFields") field = fields[form.fields.currentIndex()] - self.duplicatesReport(form.webView, field, search_text, form) + QueryOp( + parent=self.browser, + op=lambda col: col.findDupes(field, search_text), + success=self.show_duplicates_report, + ).run_in_background() search = form.buttonBox.addButton( tr.actions_search(), QDialogButtonBox.ActionRole ) - qconnect(search.clicked, onClick) + qconnect(search.clicked, on_click) self.show() - def duplicatesReport( - self, - web: AnkiWebView, - fname: str, - search: str, - frm: aqt.forms.finddupes.Ui_Dialog, - ) -> None: - self.mw.progress.start() - try: - res = self.mw.col.findDupes(fname, search) - except Exception as e: - self.mw.progress.finish() - showWarning(str(e)) - return + def show_duplicates_report(self, dupes: List[Tuple[str, List[NoteId]]]) -> None: if not self._dupesButton: - self._dupesButton = b = frm.buttonBox.addButton( + self._dupesButton = b = self.form.buttonBox.addButton( tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole ) - qconnect(b.clicked, lambda: self._onTagDupes(res)) - t = "" - groups = len(res) - notes = sum(len(r[1]) for r in res) + qconnect(b.clicked, lambda: self._tag_duplicates(dupes)) + text = "" + groups = len(dupes) + notes = sum(len(r[1]) for r in dupes) part1 = tr.browsing_group(count=groups) part2 = tr.browsing_note_count(count=notes) - t += tr.browsing_found_as_across_bs(part=part1, whole=part2) - t += "

    " - for val, nids in res: - t += ( + text += tr.browsing_found_as_across_bs(part=part1, whole=part2) + text += "

      " + for val, nids in dupes: + text += ( """
    1. %s: %s""" % ( html.escape( @@ -111,24 +101,23 @@ class FindDuplicatesDialog(QDialog): html.escape(val), ) ) - t += "
    " - web.stdHtml(t, context=self) - self.mw.progress.finish() + text += "
" + self.form.webView.stdHtml(text, context=self) - def _onTagDupes(self, res: List[Any]) -> None: - if not res: + def _tag_duplicates(self, dupes: List[Tuple[str, List[NoteId]]]) -> None: + if not dupes: return - self.browser.begin_reset() - self.mw.checkpoint(tr.browsing_tag_duplicates()) - nids = set() - for _, nidlist in res: - nids.update(nidlist) - self.mw.col.tags.bulk_add(list(nids), tr.browsing_duplicate()) - self.mw.progress.finish() - self.browser.end_reset() - self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self) - tooltip(tr.browsing_notes_tagged()) - def dupeLinkClicked(self, link: str) -> None: + note_ids = set() + for _, nids in dupes: + note_ids.update(nids) + + add_tags_to_notes( + parent=self, + note_ids=list(note_ids), + space_separated_tags=tr.browsing_duplicate(), + ).run_in_background() + + def _on_duplicate_clicked(self, link: str) -> None: self.browser.search_for(link) self.browser.onNote() diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c9a6600b3..a73841127 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -771,6 +771,8 @@ class AnkiQt(QMainWindow): reason: Any = None, context: Any = None, ) -> None: + traceback.print_stack(file=sys.stdout) + print("requireReset() is obsolete; please use CollectionOp()") self.reset() def maybeReset(self) -> None: diff --git a/qt/aqt/taskman.py b/qt/aqt/taskman.py index a1759ed1c..18c1bdbfc 100644 --- a/qt/aqt/taskman.py +++ b/qt/aqt/taskman.py @@ -4,7 +4,7 @@ """ Helper for running tasks on background threads. -See mw.query_op() and CollectionOp() for higher-level routines. +See QueryOp() and CollectionOp() for higher-level routines. """ from __future__ import annotations @@ -45,7 +45,9 @@ class TaskManager(QObject): on_done: Optional[Callable[[Future], None]] = None, args: Optional[Dict[str, Any]] = None, ) -> Future: - """Run task on a background thread. + """Use QueryOp()/CollectionOp() in new code. + + Run task on a background thread. If on_done is provided, it will be called on the main thread with the completed future. @@ -79,6 +81,7 @@ class TaskManager(QObject): label: Optional[str] = None, immediate: bool = False, ) -> None: + "Use QueryOp()/CollectionOp() in new code." self.mw.progress.start(parent=parent, label=label, immediate=immediate) def wrapped_done(fut: Future) -> None: