diff --git a/ftl/core/errors.ftl b/ftl/core/errors.ftl index 5f5ea6eb5..23a8cc7a3 100644 --- a/ftl/core/errors.ftl +++ b/ftl/core/errors.ftl @@ -1,3 +1,4 @@ errors-invalid-input-empty = Invalid input. errors-invalid-input-details = Invalid input: { $details } errors-parse-number-fail = A number was invalid or out of range. +errors-filtered-parent-deck = Invalid deck name: Filtered decks cannot be parent decks. diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 17efb366e..8e7b7c755 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -12,7 +12,7 @@ from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union import anki # pylint: disable=unused-import import anki._backend.backend_pb2 as _pb from anki.consts import * -from anki.errors import DeckIsFilteredError, DeckRenameError, NotFoundError +from anki.errors import NotFoundError from anki.utils import from_json_bytes, ids2str, intTime, to_json_bytes # public exports @@ -246,12 +246,9 @@ class DeckManager: def update(self, g: Deck, preserve_usn: bool = True) -> None: "Add or update an existing deck. Used for syncing and merging." - try: - g["id"] = self.col._backend.add_or_update_deck_legacy( - deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn - ) - except DeckIsFilteredError as exc: - raise DeckRenameError("deck was filtered") from exc + g["id"] = self.col._backend.add_or_update_deck_legacy( + deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn + ) def rename(self, g: Deck, newName: str) -> None: "Rename deck prefix to NAME if not exists. Updates children." diff --git a/pylib/anki/errors.py b/pylib/anki/errors.py index 0fd0a5abb..7ca87996d 100644 --- a/pylib/anki/errors.py +++ b/pylib/anki/errors.py @@ -47,7 +47,15 @@ class ExistsError(Exception): pass -class DeckIsFilteredError(Exception): +class DeckRenameError(Exception): + """Legacy error, use DeckIsFilteredError instead.""" + + def __init__(self, description: str, *args: object) -> None: + super().__init__(description, *args) + self.description = description + + +class DeckIsFilteredError(StringError, DeckRenameError): pass @@ -78,7 +86,7 @@ def backend_exception_to_pylib(err: _pb.BackendError) -> Exception: elif val == "exists": return ExistsError() elif val == "deck_is_filtered": - return DeckIsFilteredError() + return DeckIsFilteredError(err.localized) elif val == "proto_error": return StringError(err.localized) else: @@ -95,12 +103,3 @@ class AnkiError(Exception): def __str__(self) -> str: return self.type - - -class DeckRenameError(Exception): - def __init__(self, description: str) -> None: - super().__init__() - self.description = description - - def __str__(self) -> str: - return f"Couldn't rename deck: {self.description}" diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index cd446fec9..9b788bafb 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -9,7 +9,7 @@ from typing import Any import aqt from anki.decks import DeckTreeNode -from anki.errors import DeckRenameError +from anki.errors import DeckIsFilteredError from anki.utils import intTime from aqt import AnkiQt, gui_hooks from aqt.qt import * @@ -88,11 +88,7 @@ class DeckBrowser: elif cmd == "import": self.mw.onImport() elif cmd == "create": - deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK)) - if deck: - self.mw.col.decks.id(deck) - gui_hooks.sidebar_should_refresh_decks() - self.refresh() + self._on_create() elif cmd == "drag": source, target = arg.split(",") self._handle_drag_and_drop(int(source), int(target or 0)) @@ -272,8 +268,8 @@ class DeckBrowser: try: self.mw.col.decks.rename(deck, newName) gui_hooks.sidebar_should_refresh_decks() - except DeckRenameError as e: - showWarning(e.description) + except DeckIsFilteredError as err: + showWarning(str(err)) return self.show() @@ -352,6 +348,17 @@ class DeckBrowser: def _onShared(self) -> None: openLink(f"{aqt.appShared}decks/") + def _on_create(self) -> None: + deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK)) + if deck: + try: + self.mw.col.decks.id(deck) + except DeckIsFilteredError as err: + showWarning(str(err)) + return + gui_hooks.sidebar_should_refresh_decks() + self.refresh() + ###################################################################### def _v1_upgrade_message(self) -> str: diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index d1c4e8e2e..0c42dd44a 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -4,8 +4,8 @@ from typing import Callable, List, Optional, Tuple import aqt from anki.collection import SearchNode -from anki.decks import Deck, DeckRenameError -from anki.errors import InvalidInput +from anki.decks import Deck +from anki.errors import DeckIsFilteredError, InvalidInput from anki.lang import without_unicode_isolation from aqt import AnkiQt, colors, gui_hooks from aqt.qt import * @@ -301,8 +301,8 @@ class DeckConf(QDialog): self.saveConf() except InvalidInput as err: show_invalid_search_error(err) - except DeckRenameError as err: - showWarning(err.description) + except DeckIsFilteredError as err: + showWarning(str(err)) else: if not self.col.sched.rebuild_filtered_deck(self.deck["id"]): if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index 788193b64..ca2e26e79 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -10,7 +10,7 @@ from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast import aqt from anki.collection import Config, SearchNode from anki.decks import DeckTreeNode -from anki.errors import DeckRenameError, InvalidInput +from anki.errors import DeckIsFilteredError, InvalidInput from anki.tags import TagTreeNode from anki.types import assert_exhaustive from aqt import colors, gui_hooks @@ -988,8 +988,8 @@ class SidebarTreeView(QTreeView): self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK)) try: self.mw.col.decks.rename(deck, new_name) - except DeckRenameError as e: - showWarning(e.description) + except DeckIsFilteredError as err: + showWarning(str(err)) return self.refresh() self.mw.deckBrowser.refresh() diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 32c656f9e..cedac958c 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -4,6 +4,7 @@ from typing import List, Optional import aqt +from anki.errors import DeckIsFilteredError from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( @@ -17,6 +18,7 @@ from aqt.utils import ( saveGeom, shortcut, showInfo, + showWarning, tr, ) @@ -162,7 +164,11 @@ class StudyDeck(QDialog): n = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=default) n = n.strip() if n: - did = self.mw.col.decks.id(n) + try: + did = self.mw.col.decks.id(n) + except DeckIsFilteredError as err: + showWarning(str(err)) + return # deck name may not be the same as user input. ex: ", :: self.name = self.mw.col.decks.name(did) # make sure we clean up reset hook when manually exiting diff --git a/rslib/src/err.rs b/rslib/src/err.rs index 6a93df75b..77f714599 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -208,6 +208,7 @@ impl AnkiError { } } AnkiError::ParseNumError => i18n.tr(TR::ErrorsParseNumberFail).into(), + AnkiError::DeckIsFiltered => i18n.tr(TR::ErrorsFilteredParentDeck).into(), _ => format!("{:?}", self), } }