diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 836fb097f..bdea0f3b7 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -39,7 +39,7 @@ from anki.cards import Card, CardId from anki.config import Config, ConfigManager from anki.consts import * from anki.dbproxy import DBProxy -from anki.decks import Deck, DeckId, DeckManager +from anki.decks import DeckId, DeckManager from anki.errors import AbortSchemaModification, DBError from anki.lang import FormatTimeSpan from anki.media import MediaManager, media_paths_from_col_path @@ -335,14 +335,6 @@ class Collection: note=note._to_backend_note(), skip_undo_entry=False ) - def get_deck(self, id: DeckId) -> Deck: - "Get a new-style deck object." - return self._backend.get_deck(id) - - def update_deck(self, deck: Deck) -> OpChanges: - "Save updates to an existing deck." - return self._backend.update_deck(deck) - getCard = get_card getNote = get_note diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 72301012d..c2a78608c 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -33,9 +33,6 @@ defaultDynamicDeck = 1 DeckDict = Dict[str, Any] DeckConfigDict = Dict[str, Any] -# currently only supports read-only access -Deck = _pb.Deck - DeckId = NewType("DeckId", int) DeckConfigId = NewType("DeckConfigId", int) @@ -290,6 +287,9 @@ class DeckManager: deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn ) + def update_dict(self, deck: DeckDict) -> OpChanges: + return self.col._backend.update_deck_legacy(json=to_json_bytes(deck)) + def rename(self, deck: Union[DeckDict, DeckId], new_name: str) -> OpChanges: "Rename deck prefix to NAME if not exists. Updates children." if isinstance(deck, int): @@ -455,15 +455,12 @@ class DeckManager: # Deck selection ############################################################# - def get_current(self) -> Deck: - return self.col._backend.get_current_deck() - def set_current(self, deck: DeckId) -> OpChanges: return self.col._backend.set_current_deck(deck) def get_current_id(self) -> DeckId: "The currently selected deck ID." - return DeckId(self.get_current().id) + return DeckId(self.col._backend.get_current_deck().id) # legacy @@ -528,7 +525,7 @@ class DeckManager: ) def deck_and_child_ids(self, deck_id: DeckId) -> List[DeckId]: - parent_name = self.col.get_deck(deck_id).name + parent_name = self.name(deck_id) out = [deck_id] out.extend(self.child_ids(parent_name)) return out diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index 7927653b2..56d2355d0 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -13,7 +13,7 @@ from anki.collection import ( SearchJoiner, SearchNode, ) -from anki.decks import Deck, DeckCollapseScope, DeckId, DeckTreeNode +from anki.decks import DeckCollapseScope, DeckId, DeckTreeNode from anki.models import NotetypeId from anki.notes import Note from anki.tags import TagTreeNode @@ -902,8 +902,8 @@ class SidebarTreeView(QTreeView): full_name = item.name_prefix + new_name deck_id = DeckId(item.id) - def after_fetch(deck: Deck) -> None: - if full_name == deck.name: + def after_fetch(name: str) -> None: + if full_name == name: return rename_deck( @@ -914,7 +914,7 @@ class SidebarTreeView(QTreeView): QueryOp( parent=self.browser, - op=lambda col: col.get_deck(deck_id), + op=lambda col: col.decks.name(deck_id), success=after_fetch, ).run_in_background() diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 6c9d57d33..35c452e6e 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -9,7 +9,7 @@ from typing import Any, Optional import aqt from anki.collection import OpChanges -from anki.decks import Deck, DeckCollapseScope, DeckId, DeckTreeNode +from anki.decks import DeckCollapseScope, DeckId, DeckTreeNode from aqt import AnkiQt, gui_hooks from aqt.deckoptions import display_options_for_deck_id from aqt.operations import QueryOp @@ -278,9 +278,9 @@ class DeckBrowser: self.mw.onExport(did=did) def _rename(self, did: DeckId) -> None: - def prompt(deck: Deck) -> None: - new_name = getOnlyText(tr.decks_new_deck_name(), default=deck.name) - if not new_name or new_name == deck.name: + def prompt(name: str) -> None: + new_name = getOnlyText(tr.decks_new_deck_name(), default=name) + if not new_name or new_name == name: return else: rename_deck( @@ -288,7 +288,7 @@ class DeckBrowser: ).run_in_background() QueryOp( - parent=self.mw, op=lambda col: col.get_deck(did), success=prompt + parent=self.mw, op=lambda col: col.decks.name(did), success=prompt ).run_in_background() def _options(self, did: DeckId) -> None: diff --git a/qt/aqt/deckdescription.py b/qt/aqt/deckdescription.py index f9dd789fb..ce87ad7ee 100644 --- a/qt/aqt/deckdescription.py +++ b/qt/aqt/deckdescription.py @@ -4,9 +4,9 @@ from __future__ import annotations import aqt -from anki.decks import Deck +from anki.decks import DeckDict from aqt.operations import QueryOp -from aqt.operations.deck import update_deck +from aqt.operations.deck import update_deck_dict from aqt.qt import * from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr @@ -21,16 +21,16 @@ class DeckDescriptionDialog(QDialog): self.mw = mw # set on success - self.deck: Deck + self.deck: DeckDict QueryOp( parent=self.mw, - op=lambda col: col.decks.get_current(), + op=lambda col: col.decks.current(), success=self._setup_and_show, ).run_in_background() - def _setup_and_show(self, deck: Deck) -> None: - if deck.WhichOneof("kind") != "normal": + def _setup_and_show(self, deck: DeckDict) -> None: + if deck["dyn"]: return self.deck = deck @@ -53,11 +53,11 @@ class DeckDescriptionDialog(QDialog): self.enable_markdown = QCheckBox(tr.deck_config_description_markdown()) self.enable_markdown.setToolTip(tr.deck_config_description_markdown_hint()) - self.enable_markdown.setChecked(self.deck.normal.markdown_description) + self.enable_markdown.setChecked(self.deck.get("md", False)) box.addWidget(self.enable_markdown) self.description = QPlainTextEdit() - self.description.setPlainText(self.deck.normal.description) + self.description.setPlainText(self.deck.get("desc", "")) box.addWidget(self.description) button_box = QDialogButtonBox() @@ -69,10 +69,10 @@ class DeckDescriptionDialog(QDialog): self.show() def save_and_accept(self) -> None: - self.deck.normal.description = self.description.toPlainText() - self.deck.normal.markdown_description = self.enable_markdown.isChecked() + self.deck["desc"] = self.description.toPlainText() + self.deck["md"] = self.enable_markdown.isChecked() - update_deck(parent=self, deck=self.deck).success( + update_deck_dict(parent=self, deck=self.deck).success( lambda _: self.accept() ).run_in_background() diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py index 33079efa0..937d949fd 100644 --- a/qt/aqt/deckoptions.py +++ b/qt/aqt/deckoptions.py @@ -8,7 +8,7 @@ from typing import List, Optional import aqt import aqt.deckconf from anki.cards import Card -from anki.decks import Deck, DeckId +from anki.decks import DeckDict, DeckId from anki.lang import without_unicode_isolation from aqt import gui_hooks from aqt.qt import * @@ -29,7 +29,7 @@ class DeckOptionsDialog(QDialog): TITLE = "deckOptions" silentlyClose = True - def __init__(self, mw: aqt.main.AnkiQt, deck: Deck) -> None: + def __init__(self, mw: aqt.main.AnkiQt, deck: DeckDict) -> None: QDialog.__init__(self, mw, Qt.Window) self.mw = mw self._deck = deck @@ -54,10 +54,10 @@ class DeckOptionsDialog(QDialog): self.web.eval( f"""const $deckOptions = anki.deckOptions( - document.getElementById('main'), {self._deck.id});""" + document.getElementById('main'), {self._deck["id"]});""" ) self.setWindowTitle( - without_unicode_isolation(tr.actions_options_for(val=self._deck.name)) + without_unicode_isolation(tr.actions_options_for(val=self._deck["name"])) ) gui_hooks.deck_options_did_load(self) @@ -68,28 +68,28 @@ class DeckOptionsDialog(QDialog): def confirm_deck_then_display_options(active_card: Optional[Card] = None) -> None: - decks = [aqt.mw.col.decks.get_current()] + decks = [aqt.mw.col.decks.current()] if card := active_card: - if card.odid and card.odid != decks[0].id: - decks.append(aqt.mw.col.get_deck(card.odid)) + if card.odid and card.odid != decks[0]["id"]: + decks.append(aqt.mw.col.decks.get(card.odid)) - if not any(d.id == card.did for d in decks): - decks.append(aqt.mw.col.get_deck(card.odid)) + if not any(d["id"] == card.did for d in decks): + decks.append(aqt.mw.col.decks.get(card.odid)) if len(decks) == 1: display_options_for_deck(decks[0]) else: - decks.sort(key=lambda x: x.WhichOneof("kind") == "filtered") + decks.sort(key=lambda x: x["dyn"]) _deck_prompt_dialog(decks) -def _deck_prompt_dialog(decks: List[Deck]) -> None: +def _deck_prompt_dialog(decks: List[DeckDict]) -> None: diag = QDialog(aqt.mw.app.activeWindow()) diag.setWindowTitle("Anki") box = QVBoxLayout() box.addWidget(QLabel(tr.deck_config_which_deck())) for deck in decks: - button = QPushButton(deck.name) + button = QPushButton(deck["name"]) qconnect(button.clicked, lambda _, deck=deck: display_options_for_deck(deck)) qconnect(button.clicked, diag.close) box.addWidget(button) @@ -101,15 +101,15 @@ def _deck_prompt_dialog(decks: List[Deck]) -> None: def display_options_for_deck_id(deck_id: DeckId) -> None: - display_options_for_deck(aqt.mw.col.get_deck(deck_id)) + display_options_for_deck(aqt.mw.col.decks.get(deck_id)) -def display_options_for_deck(deck: Deck) -> None: - if deck.WhichOneof("kind") == "normal": +def display_options_for_deck(deck: DeckDict) -> None: + if not deck["dyn"]: if KeyboardModifiersPressed().shift or aqt.mw.col.schedVer() == 1: - deck_legacy = aqt.mw.col.decks.get(DeckId(deck.id)) + deck_legacy = aqt.mw.col.decks.get(DeckId(deck["id"])) aqt.deckconf.DeckConf(aqt.mw, deck_legacy) else: DeckOptionsDialog(aqt.mw, deck) else: - aqt.dialogs.open("FilteredDeckConfigDialog", aqt.mw, deck_id=deck.id) + aqt.dialogs.open("FilteredDeckConfigDialog", aqt.mw, deck_id=deck["id"]) diff --git a/qt/aqt/operations/deck.py b/qt/aqt/operations/deck.py index 867f3992a..9edc99c9c 100644 --- a/qt/aqt/operations/deck.py +++ b/qt/aqt/operations/deck.py @@ -6,7 +6,7 @@ from __future__ import annotations from typing import Optional, Sequence from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId -from anki.decks import Deck, DeckCollapseScope, DeckId, UpdateDeckConfigs +from anki.decks import DeckCollapseScope, DeckDict, DeckId, UpdateDeckConfigs from aqt import QWidget from aqt.operations import CollectionOp from aqt.utils import getOnlyText, tooltip, tr @@ -88,5 +88,5 @@ def update_deck_configs( return CollectionOp(parent, lambda col: col.decks.update_deck_configs(input)) -def update_deck(*, parent: QWidget, deck: Deck) -> CollectionOp[OpChanges]: - return CollectionOp(parent, lambda col: col.update_deck(deck)) +def update_deck_dict(*, parent: QWidget, deck: DeckDict) -> CollectionOp[OpChanges]: + return CollectionOp(parent, lambda col: col.decks.update_dict(deck)) diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 117ffaa10..19dfd1f9b 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -94,7 +94,7 @@ class Overview: elif url == "anki": print("anki menu") elif url == "opts": - display_options_for_deck(self.mw.col.decks.get_current()) + display_options_for_deck(self.mw.col.decks.current()) elif url == "cram": aqt.dialogs.open("FilteredDeckConfigDialog", self.mw) elif url == "refresh": @@ -117,7 +117,7 @@ class Overview: def _shortcutKeys(self) -> List[Tuple[str, Callable]]: return [ - ("o", lambda: display_options_for_deck(self.mw.col.decks.get_current())), + ("o", lambda: display_options_for_deck(self.mw.col.decks.current())), ("r", self.rebuild_current_filtered_deck), ("e", self.empty_current_filtered_deck), ("c", self.onCustomStudyKey), diff --git a/rslib/backend.proto b/rslib/backend.proto index 1fe3b61c6..0b8e33631 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -144,6 +144,7 @@ service DecksService { rpc GetDeckIdByName(String) returns (DeckId); rpc GetDeck(DeckId) returns (Deck); rpc UpdateDeck(Deck) returns (OpChanges); + rpc UpdateDeckLegacy(Json) returns (OpChanges); rpc SetDeckCollapsed(SetDeckCollapsedIn) returns (OpChanges); rpc GetDeckLegacy(DeckId) returns (Json); rpc GetDeckNames(GetDeckNamesIn) returns (DeckNames); diff --git a/rslib/src/backend/decks.rs b/rslib/src/backend/decks.rs index ebe2ca96d..1a179d7ab 100644 --- a/rslib/src/backend/decks.rs +++ b/rslib/src/backend/decks.rs @@ -97,6 +97,14 @@ impl DecksService for Backend { }) } + fn update_deck_legacy(&self, input: pb::Json) -> Result { + self.with_col(|col| { + let deck: DeckSchema11 = serde_json::from_slice(&input.json)?; + let mut deck = deck.into(); + col.update_deck(&mut deck).map(Into::into) + }) + } + fn get_deck_legacy(&self, input: pb::DeckId) -> Result { self.with_col(|col| { let deck: DeckSchema11 = col