use perform_op() for deck creation

This commit is contained in:
Damien Elmes 2021-03-22 23:17:07 +10:00
parent 0123a382ec
commit 01161c8ed2
12 changed files with 142 additions and 54 deletions

View File

@ -17,6 +17,7 @@ Preferences = _pb.Preferences
UndoStatus = _pb.UndoStatus
OpChanges = _pb.OpChanges
OpChangesWithCount = _pb.OpChangesWithCount
OpChangesWithID = _pb.OpChangesWithID
DefaultsForAdding = _pb.DeckAndNotetype
import copy

View File

@ -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:

View File

@ -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,
)

View File

@ -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)
######################################################################

View File

@ -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]]

View File

@ -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)

View File

@ -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 {

View File

@ -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<pb::OpChangesWithId> {
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<pb::DeckId> {
self.with_col(|col| {
let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?;
@ -148,3 +157,40 @@ impl From<DeckID> 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<pb::Deck> {
// let deck = if input.val {
// Deck::new_filtered()
// } else {
// Deck::new_normal()
// };
// Ok(deck.into())
// }
// impl From<pb::Deck> 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<pb::deck::Kind> 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),
// }
// }
// }

View File

@ -80,12 +80,3 @@ impl From<Vec<String>> for pb::StringList {
pb::StringList { vals }
}
}
impl From<OpOutput<usize>> for pb::OpChangesWithCount {
fn from(out: OpOutput<usize>) -> Self {
pb::OpChangesWithCount {
count: out.output as u32,
changes: Some(out.changes.into()),
}
}
}

View File

@ -44,3 +44,21 @@ impl From<OpOutput<()>> for pb::OpChanges {
o.changes.into()
}
}
impl From<OpOutput<usize>> for pb::OpChangesWithCount {
fn from(out: OpOutput<usize>) -> Self {
pb::OpChangesWithCount {
count: out.output as u32,
changes: Some(out.changes.into()),
}
}
}
impl From<OpOutput<i64>> for pb::OpChangesWithId {
fn from(out: OpOutput<i64>) -> Self {
pb::OpChangesWithId {
id: out.output,
changes: Some(out.changes.into()),
}
}
}

View File

@ -207,7 +207,7 @@ impl From<Deck> 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()),

View File

@ -86,3 +86,15 @@ pub struct OpOutput<T> {
pub output: T,
pub changes: OpChanges,
}
impl<T> OpOutput<T> {
pub(crate) fn map<F, N>(self, func: F) -> OpOutput<N>
where
F: FnOnce(T) -> N,
{
OpOutput {
output: func(self.output),
changes: self.changes,
}
}
}