mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
update find_duplicates to use QueryOp/CollectionOp
This commit is contained in:
parent
f2db822c08
commit
1918031399
4 changed files with 48 additions and 54 deletions
|
@ -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))
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue