From 01161c8ed209c7c1535c93bb4184c1b25aa86739 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 22 Mar 2021 23:17:07 +1000 Subject: [PATCH] use perform_op() for deck creation --- pylib/anki/collection.py | 1 + pylib/anki/decks.py | 21 ++++++++++++---- qt/aqt/deck_ops.py | 25 +++++++++++++++++++- qt/aqt/deckbrowser.py | 24 +++---------------- qt/aqt/main.py | 4 +++- qt/aqt/studydeck.py | 26 ++++++++------------ rslib/backend.proto | 8 ++++++- rslib/src/backend/decks.rs | 46 ++++++++++++++++++++++++++++++++++++ rslib/src/backend/generic.rs | 9 ------- rslib/src/backend/ops.rs | 18 ++++++++++++++ rslib/src/decks/mod.rs | 2 +- rslib/src/ops.rs | 12 ++++++++++ 12 files changed, 142 insertions(+), 54 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 01b0fb3c4..01fc65d46 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -17,6 +17,7 @@ Preferences = _pb.Preferences UndoStatus = _pb.UndoStatus OpChanges = _pb.OpChanges OpChangesWithCount = _pb.OpChangesWithCount +OpChangesWithID = _pb.OpChangesWithID DefaultsForAdding = _pb.DeckAndNotetype import copy diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 7a7a40765..726b4c46d 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -11,7 +11,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.collection import OpChanges, OpChangesWithCount +from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithID from anki.consts import * from anki.errors import NotFoundError from anki.utils import from_json_bytes, ids2str, intTime, legacy_func, to_json_bytes @@ -112,6 +112,20 @@ class DeckManager: # Deck save/load ############################################################# + def add_normal_deck_with_name(self, name: str) -> OpChangesWithID: + "If deck exists, return existing id." + if id := self.col.decks.id_for_name(name): + return OpChangesWithID(id=id) + else: + deck = self.col.decks.new_deck_legacy(filtered=False) + deck["name"] = name + return self.add_deck_legacy(deck) + + def add_deck_legacy(self, deck: Deck) -> OpChangesWithID: + "Add a deck created with new_deck_legacy(). Must have id of 0." + assert deck["id"] == 0 + return self.col._backend.add_deck_legacy(to_json_bytes(deck)) + def id( self, name: str, @@ -127,9 +141,8 @@ class DeckManager: deck = self.new_deck_legacy(bool(type)) deck["name"] = name - self.update(deck, preserve_usn=False) - - return deck["id"] + out = self.add_deck_legacy(deck) + return out.id @legacy_func(sub="remove") def rem(self, did: int, cardsToo: bool = True, childrenToo: bool = True) -> None: diff --git a/qt/aqt/deck_ops.py b/qt/aqt/deck_ops.py index 1c310d552..1e5cbff15 100644 --- a/qt/aqt/deck_ops.py +++ b/qt/aqt/deck_ops.py @@ -8,7 +8,8 @@ from typing import Callable, Sequence from anki.decks import DeckID from anki.lang import TR from aqt import AnkiQt, QWidget -from aqt.utils import tooltip, tr +from aqt.main import PerformOpOptionalSuccessCallback +from aqt.utils import getOnlyText, tooltip, tr def remove_decks( @@ -46,3 +47,25 @@ def rename_deck( mw.perform_op( lambda: mw.col.decks.rename(deck_id, new_name), after_hooks=after_rename ) + + +def add_deck_dialog( + *, + mw: AnkiQt, + parent: QWidget, + default_text: str = "", + success: PerformOpOptionalSuccessCallback = None, +) -> None: + if name := getOnlyText( + tr(TR.DECKS_NEW_DECK_NAME), default=default_text, parent=parent + ).strip(): + add_deck(mw=mw, name=name, success=success) + + +def add_deck( + *, mw: AnkiQt, name: str, success: PerformOpOptionalSuccessCallback = None +) -> None: + mw.perform_op( + lambda: mw.col.decks.add_normal_deck_with_name(name), + success=success, + ) diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 916d962b9..e9dc373ef 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -10,23 +10,13 @@ from typing import Any import aqt from anki.collection import OpChanges from anki.decks import DeckTreeNode -from anki.errors import DeckIsFilteredError from anki.utils import intTime from aqt import AnkiQt, gui_hooks -from aqt.deck_ops import remove_decks, rename_deck, reparent_decks +from aqt.deck_ops import add_deck_dialog, remove_decks, rename_deck, reparent_decks from aqt.qt import * from aqt.sound import av_player from aqt.toolbar import BottomBar -from aqt.utils import ( - TR, - askUser, - getOnlyText, - openLink, - shortcut, - showInfo, - showWarning, - tr, -) +from aqt.utils import TR, askUser, getOnlyText, openLink, shortcut, showInfo, tr class DeckBrowserBottomBar: @@ -331,15 +321,7 @@ class DeckBrowser: 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() + add_deck_dialog(mw=self.mw, parent=self.mw) ###################################################################### diff --git a/qt/aqt/main.py b/qt/aqt/main.py index f8d09ff1d..73b96172b 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -47,6 +47,7 @@ from anki.collection import ( Config, OpChanges, OpChangesWithCount, + OpChangesWithID, ReviewUndo, UndoResult, UndoStatus, @@ -102,7 +103,8 @@ class HasChangesProperty(Protocol): # doesn't actually work for protobuf objects, so new protobuf objects will # either need to be added here, or cast at call time ResultWithChanges = TypeVar( - "ResultWithChanges", bound=Union[OpChanges, OpChangesWithCount, HasChangesProperty] + "ResultWithChanges", + bound=Union[OpChanges, OpChangesWithCount, OpChangesWithID, HasChangesProperty], ) PerformOpOptionalSuccessCallback = Optional[Callable[[ResultWithChanges], Any]] diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 6f5df7161..6e18a3e8a 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -4,21 +4,20 @@ from typing import List, Optional import aqt -from anki.errors import DeckIsFilteredError +from anki.collection import OpChangesWithID from aqt import gui_hooks +from aqt.deck_ops import add_deck_dialog from aqt.qt import * from aqt.utils import ( TR, HelpPage, HelpPageArgument, disable_help_button, - getOnlyText, openHelp, restoreGeom, saveGeom, shortcut, showInfo, - showWarning, tr, ) @@ -166,19 +165,14 @@ class StudyDeck(QDialog): default = self.form.filter.text() else: default = self.names[self.form.list.currentRow()] - n = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=default) - n = n.strip() - if 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) + + def success(out: OpChangesWithID) -> None: + deck = self.mw.col.decks.get(out.id) + self.name = deck["name"] + # make sure we clean up reset hook when manually exiting gui_hooks.state_did_reset.remove(self.onReset) - if self.mw.state == "deckBrowser": - self.mw.deckBrowser.refresh() - gui_hooks.sidebar_should_refresh_decks() + QDialog.accept(self) + + add_deck_dialog(mw=self.mw, parent=self, default_text=default, success=success) diff --git a/rslib/backend.proto b/rslib/backend.proto index 9877dba61..c3d7b8ab9 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -50,6 +50,11 @@ message OpChangesWithCount { OpChanges changes = 2; } +message OpChangesWithID { + int64 id = 1; + OpChanges changes = 2; +} + // IDs used in RPC calls /////////////////////////////////////////////////////////// @@ -131,6 +136,7 @@ service SchedulingService { } service DecksService { + rpc AddDeckLegacy(Json) returns (OpChangesWithID); rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyIn) returns (DeckID); rpc DeckTree(DeckTreeIn) returns (DeckTreeNode); rpc DeckTreeLegacy(Empty) returns (Json); @@ -459,7 +465,7 @@ message CardRequirement { message Deck { int64 id = 1; string name = 2; - uint32 mtime_secs = 3; + int64 mtime_secs = 3; int32 usn = 4; DeckCommon common = 5; oneof kind { diff --git a/rslib/src/backend/decks.rs b/rslib/src/backend/decks.rs index fe725f0c5..a7b73da83 100644 --- a/rslib/src/backend/decks.rs +++ b/rslib/src/backend/decks.rs @@ -10,6 +10,15 @@ use crate::{ pub(super) use pb::decks_service::Service as DecksService; impl DecksService for Backend { + fn add_deck_legacy(&self, input: pb::Json) -> Result { + let schema11: DeckSchema11 = serde_json::from_slice(&input.json)?; + let mut deck: Deck = schema11.into(); + self.with_col(|col| { + let output = col.add_deck(&mut deck)?; + Ok(output.map(|_| deck.id.0).into()) + }) + } + fn add_or_update_deck_legacy(&self, input: pb::AddOrUpdateDeckLegacyIn) -> Result { self.with_col(|col| { let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?; @@ -148,3 +157,40 @@ impl From for pb::DeckId { pb::DeckId { did: did.0 } } } + +// before we can switch to returning protobuf, we need to make sure we're converting the +// deck separators + +// fn new_deck(&self, input: pb::Bool) -> Result { +// let deck = if input.val { +// Deck::new_filtered() +// } else { +// Deck::new_normal() +// }; +// Ok(deck.into()) +// } + +// impl From for Deck { +// fn from(deck: pb::Deck) -> Self { +// Self { +// id: deck.id.into(), +// name: deck.name, +// mtime_secs: deck.mtime_secs.into(), +// usn: deck.usn.into(), +// common: deck.common.unwrap_or_default(), +// kind: deck +// .kind +// .map(Into::into) +// .unwrap_or_else(|| DeckKind::Normal(NormalDeck::default())), +// } +// } +// } + +// impl From for DeckKind { +// fn from(kind: pb::deck::Kind) -> Self { +// match kind { +// pb::deck::Kind::Normal(normal) => DeckKind::Normal(normal), +// pb::deck::Kind::Filtered(filtered) => DeckKind::Filtered(filtered), +// } +// } +// } diff --git a/rslib/src/backend/generic.rs b/rslib/src/backend/generic.rs index 4595e279f..3aae3b721 100644 --- a/rslib/src/backend/generic.rs +++ b/rslib/src/backend/generic.rs @@ -80,12 +80,3 @@ impl From> for pb::StringList { pb::StringList { vals } } } - -impl From> for pb::OpChangesWithCount { - fn from(out: OpOutput) -> Self { - pb::OpChangesWithCount { - count: out.output as u32, - changes: Some(out.changes.into()), - } - } -} diff --git a/rslib/src/backend/ops.rs b/rslib/src/backend/ops.rs index b25c51de5..22c39ac36 100644 --- a/rslib/src/backend/ops.rs +++ b/rslib/src/backend/ops.rs @@ -44,3 +44,21 @@ impl From> for pb::OpChanges { o.changes.into() } } + +impl From> for pb::OpChangesWithCount { + fn from(out: OpOutput) -> Self { + pb::OpChangesWithCount { + count: out.output as u32, + changes: Some(out.changes.into()), + } + } +} + +impl From> for pb::OpChangesWithId { + fn from(out: OpOutput) -> Self { + pb::OpChangesWithId { + id: out.output, + changes: Some(out.changes.into()), + } + } +} diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index 0e5a9afa5..8c1029436 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -207,7 +207,7 @@ impl From for DeckProto { DeckProto { id: d.id.0, name: d.name, - mtime_secs: d.mtime_secs.0 as u32, + mtime_secs: d.mtime_secs.0, usn: d.usn.0, common: Some(d.common), kind: Some(d.kind.into()), diff --git a/rslib/src/ops.rs b/rslib/src/ops.rs index a8543f0a8..980c16e04 100644 --- a/rslib/src/ops.rs +++ b/rslib/src/ops.rs @@ -86,3 +86,15 @@ pub struct OpOutput { pub output: T, pub changes: OpChanges, } + +impl OpOutput { + pub(crate) fn map(self, func: F) -> OpOutput + where + F: FnOnce(T) -> N, + { + OpOutput { + output: func(self.output), + changes: self.changes, + } + } +}