do tag rename and tag clearing in background; move logic to tags.py

Because the logic is in rename_tag() now, it means we create a
checkpoint even if the tag is orphaned. This is because currently
checkpointing is a GUI responsibility. In the future we need to introduce
multi-level undo, and should move responsibility for managing it
to the backend.
This commit is contained in:
Damien Elmes 2021-01-04 14:13:20 +10:00
parent 35ce1b0d29
commit f3fa9daae2
3 changed files with 37 additions and 18 deletions

View file

@ -13,7 +13,7 @@ from __future__ import annotations
import pprint import pprint
import re import re
from typing import Collection, List, Optional, Tuple from typing import Collection, List, Optional, Sequence, Tuple
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
from anki.utils import ids2str from anki.utils import ids2str
@ -92,7 +92,7 @@ class TagManager:
return self.col.backend.add_note_tags(nids=nids, tags=tags) return self.col.backend.add_note_tags(nids=nids, tags=tags)
def bulk_update( def bulk_update(
self, nids: List[int], tags: str, replacement: str, regex: bool self, nids: Sequence[int], tags: str, replacement: str, regex: bool
) -> int: ) -> int:
"""Replace space-separated tags, returning changed count. """Replace space-separated tags, returning changed count.
Tags replaced with an empty string will be removed.""" Tags replaced with an empty string will be removed."""
@ -100,6 +100,15 @@ class TagManager:
nids=nids, tags=tags, replacement=replacement, regex=regex nids=nids, tags=tags, replacement=replacement, regex=regex
) )
def rename_tag(self, old: str, new: str) -> int:
"Rename provided tag, returning number of changed notes."
escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old)
escaped_name = '"{}"'.format(escaped_name.replace('"', '\\"'))
nids = self.col.find_notes("tag:" + escaped_name)
if not nids:
return 0
return self.col.tags.bulk_update(nids, old, new, False)
# legacy routines # legacy routines
def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None: def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None:

View file

@ -6,6 +6,7 @@ from __future__ import annotations
import html import html
import re import re
import time import time
from concurrent.futures import Future
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from operator import itemgetter from operator import itemgetter
@ -1649,8 +1650,11 @@ where id in %s"""
self.editor.saveNow(self._clearUnusedTags) self.editor.saveNow(self._clearUnusedTags)
def _clearUnusedTags(self): def _clearUnusedTags(self):
self.col.tags.registerNotes() def on_done(fut: Future):
self.on_tag_list_update() fut.result()
self.on_tag_list_update()
self.mw.taskman.run_in_background(self.col.tags.registerNotes, on_done)
# Suspending # Suspending
###################################################################### ######################################################################

View file

@ -4,6 +4,7 @@
from __future__ import annotations from __future__ import annotations
import re import re
from concurrent.futures import Future
from enum import Enum from enum import Enum
import aqt import aqt
@ -92,7 +93,7 @@ class NewSidebarTreeView(SidebarTreeViewBase):
a.triggered.connect(lambda _, func=act_func: func(item)) # type: ignore a.triggered.connect(lambda _, func=act_func: func(item)) # type: ignore
m.exec_(QCursor.pos()) m.exec_(QCursor.pos())
def rename_deck(self, item: aqt.browser.SidebarItem) -> None: def rename_deck(self, item: "aqt.browser.SidebarItem") -> None:
deck = self.mw.col.decks.get(item.id) deck = self.mw.col.decks.get(item.id)
old_name = deck["name"] old_name = deck["name"]
new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name) new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name)
@ -107,24 +108,29 @@ class NewSidebarTreeView(SidebarTreeViewBase):
self.browser.maybeRefreshSidebar() self.browser.maybeRefreshSidebar()
self.mw.deckBrowser.refresh() self.mw.deckBrowser.refresh()
def rename_tag(self, item: aqt.browser.SidebarItem) -> None: def rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
self.browser.editor.saveNow(lambda: self._rename_tag(item)) self.browser.editor.saveNow(lambda: self._rename_tag(item))
def _rename_tag(self, item: aqt.browser.SidebarItem) -> None: def _rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
old_name = item.name old_name = item.name
escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old_name)
escaped_name = '"{}"'.format(escaped_name.replace('"', '\\"'))
nids = self.col.find_notes("tag:" + escaped_name)
if len(nids) == 0:
showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY))
return
new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name) new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name)
if new_name == old_name or not new_name: if new_name == old_name or not new_name:
return return
def do_rename():
return self.col.tags.rename_tag(old_name, new_name)
def on_done(fut: Future):
self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self)
self.browser.model.endReset()
count = fut.result()
if not count:
showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY))
return
self.browser.clearUnusedTags()
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG)) self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG))
self.browser.model.beginReset() self.browser.model.beginReset()
self.col.tags.bulk_update(list(nids), old_name, new_name, False) self.mw.taskman.run_in_background(do_rename, on_done)
self.browser.model.endReset()
self.browser.clearUnusedTags()
self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self)
self.browser.maybeRefreshSidebar()