update find_duplicates to use QueryOp/CollectionOp

This commit is contained in:
Damien Elmes 2021-05-08 16:56:51 +10:00
parent f2db822c08
commit 1918031399
4 changed files with 48 additions and 54 deletions

View file

@ -608,7 +608,7 @@ class Collection:
return self._backend.field_names_for_notes(nids) return self._backend.field_names_for_notes(nids)
# returns array of ("dupestr", [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( nids = self.find_notes(
self.build_search_string(search, SearchNode(field_name=fieldName)) self.build_search_string(search, SearchNode(field_name=fieldName))
) )

View file

@ -4,15 +4,17 @@
from __future__ import annotations from __future__ import annotations
import html import html
from typing import Any, List, Optional from typing import Any, List, Optional, Tuple
import anki import anki
import anki.find import anki.find
import aqt import aqt
from anki.collection import SearchNode from anki.collection import SearchNode
from anki.notes import NoteId
from aqt.qt import * from aqt.qt import *
from ..main import ResetReason from ..operations import QueryOp
from ..operations.tag import add_tags_to_notes
from ..utils import ( from ..utils import (
disable_help_button, disable_help_button,
restore_combo_history, restore_combo_history,
@ -21,11 +23,8 @@ from ..utils import (
save_combo_history, save_combo_history,
save_combo_index_for_session, save_combo_index_for_session,
saveGeom, saveGeom,
showWarning,
tooltip,
tr, tr,
) )
from ..webview import AnkiWebView
from . import Browser from . import Browser
@ -35,7 +34,7 @@ class FindDuplicatesDialog(QDialog):
self.browser = browser self.browser = browser
self.mw = mw self.mw = mw
self.mw.garbage_collect_on_dialog_finish(self) 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) form.setupUi(self)
restoreGeom(self, "findDupes") restoreGeom(self, "findDupes")
disable_help_button(self) disable_help_button(self)
@ -50,56 +49,47 @@ class FindDuplicatesDialog(QDialog):
# links # links
form.webView.set_title("find duplicates") 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) form.webView.stdHtml("", context=self)
def onFin(code: Any) -> None: def on_finished(code: Any) -> None:
saveGeom(self, "findDupes") saveGeom(self, "findDupes")
qconnect(self.finished, onFin) qconnect(self.finished, on_finished)
def onClick() -> None: def on_click() -> None:
search_text = save_combo_history( search_text = save_combo_history(
form.search, searchHistory, "findDupesFind" form.search, searchHistory, "findDupesFind"
) )
save_combo_index_for_session(form.fields, "findDupesFields") save_combo_index_for_session(form.fields, "findDupesFields")
field = fields[form.fields.currentIndex()] 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( search = form.buttonBox.addButton(
tr.actions_search(), QDialogButtonBox.ActionRole tr.actions_search(), QDialogButtonBox.ActionRole
) )
qconnect(search.clicked, onClick) qconnect(search.clicked, on_click)
self.show() self.show()
def duplicatesReport( def show_duplicates_report(self, dupes: List[Tuple[str, List[NoteId]]]) -> None:
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
if not self._dupesButton: if not self._dupesButton:
self._dupesButton = b = frm.buttonBox.addButton( self._dupesButton = b = self.form.buttonBox.addButton(
tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole
) )
qconnect(b.clicked, lambda: self._onTagDupes(res)) qconnect(b.clicked, lambda: self._tag_duplicates(dupes))
t = "" text = ""
groups = len(res) groups = len(dupes)
notes = sum(len(r[1]) for r in res) notes = sum(len(r[1]) for r in dupes)
part1 = tr.browsing_group(count=groups) part1 = tr.browsing_group(count=groups)
part2 = tr.browsing_note_count(count=notes) part2 = tr.browsing_note_count(count=notes)
t += tr.browsing_found_as_across_bs(part=part1, whole=part2) text += tr.browsing_found_as_across_bs(part=part1, whole=part2)
t += "<p><ol>" text += "<p><ol>"
for val, nids in res: for val, nids in dupes:
t += ( text += (
"""<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>""" """<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>"""
% ( % (
html.escape( html.escape(
@ -111,24 +101,23 @@ class FindDuplicatesDialog(QDialog):
html.escape(val), html.escape(val),
) )
) )
t += "</ol>" text += "</ol>"
web.stdHtml(t, context=self) self.form.webView.stdHtml(text, context=self)
self.mw.progress.finish()
def _onTagDupes(self, res: List[Any]) -> None: def _tag_duplicates(self, dupes: List[Tuple[str, List[NoteId]]]) -> None:
if not res: if not dupes:
return 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.search_for(link)
self.browser.onNote() self.browser.onNote()

View file

@ -771,6 +771,8 @@ class AnkiQt(QMainWindow):
reason: Any = None, reason: Any = None,
context: Any = None, context: Any = None,
) -> None: ) -> None:
traceback.print_stack(file=sys.stdout)
print("requireReset() is obsolete; please use CollectionOp()")
self.reset() self.reset()
def maybeReset(self) -> None: def maybeReset(self) -> None:

View file

@ -4,7 +4,7 @@
""" """
Helper for running tasks on background threads. 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 from __future__ import annotations
@ -45,7 +45,9 @@ class TaskManager(QObject):
on_done: Optional[Callable[[Future], None]] = None, on_done: Optional[Callable[[Future], None]] = None,
args: Optional[Dict[str, Any]] = None, args: Optional[Dict[str, Any]] = None,
) -> Future: ) -> 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 If on_done is provided, it will be called on the main thread with
the completed future. the completed future.
@ -79,6 +81,7 @@ class TaskManager(QObject):
label: Optional[str] = None, label: Optional[str] = None,
immediate: bool = False, immediate: bool = False,
) -> None: ) -> None:
"Use QueryOp()/CollectionOp() in new code."
self.mw.progress.start(parent=parent, label=label, immediate=immediate) self.mw.progress.start(parent=parent, label=label, immediate=immediate)
def wrapped_done(fut: Future) -> None: def wrapped_done(fut: Future) -> None: