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 UndoStatus = _pb.UndoStatus
OpChanges = _pb.OpChanges OpChanges = _pb.OpChanges
OpChangesWithCount = _pb.OpChangesWithCount OpChangesWithCount = _pb.OpChangesWithCount
OpChangesWithID = _pb.OpChangesWithID
DefaultsForAdding = _pb.DeckAndNotetype DefaultsForAdding = _pb.DeckAndNotetype
import copy 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 # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb 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.consts import *
from anki.errors import NotFoundError from anki.errors import NotFoundError
from anki.utils import from_json_bytes, ids2str, intTime, legacy_func, to_json_bytes from anki.utils import from_json_bytes, ids2str, intTime, legacy_func, to_json_bytes
@ -112,6 +112,20 @@ class DeckManager:
# Deck save/load # 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( def id(
self, self,
name: str, name: str,
@ -127,9 +141,8 @@ class DeckManager:
deck = self.new_deck_legacy(bool(type)) deck = self.new_deck_legacy(bool(type))
deck["name"] = name deck["name"] = name
self.update(deck, preserve_usn=False) out = self.add_deck_legacy(deck)
return out.id
return deck["id"]
@legacy_func(sub="remove") @legacy_func(sub="remove")
def rem(self, did: int, cardsToo: bool = True, childrenToo: bool = True) -> None: 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.decks import DeckID
from anki.lang import TR from anki.lang import TR
from aqt import AnkiQt, QWidget 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( def remove_decks(
@ -46,3 +47,25 @@ def rename_deck(
mw.perform_op( mw.perform_op(
lambda: mw.col.decks.rename(deck_id, new_name), after_hooks=after_rename 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 import aqt
from anki.collection import OpChanges from anki.collection import OpChanges
from anki.decks import DeckTreeNode from anki.decks import DeckTreeNode
from anki.errors import DeckIsFilteredError
from anki.utils import intTime from anki.utils import intTime
from aqt import AnkiQt, gui_hooks 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.qt import *
from aqt.sound import av_player from aqt.sound import av_player
from aqt.toolbar import BottomBar from aqt.toolbar import BottomBar
from aqt.utils import ( from aqt.utils import TR, askUser, getOnlyText, openLink, shortcut, showInfo, tr
TR,
askUser,
getOnlyText,
openLink,
shortcut,
showInfo,
showWarning,
tr,
)
class DeckBrowserBottomBar: class DeckBrowserBottomBar:
@ -331,15 +321,7 @@ class DeckBrowser:
openLink(f"{aqt.appShared}decks/") openLink(f"{aqt.appShared}decks/")
def _on_create(self) -> None: def _on_create(self) -> None:
deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK)) add_deck_dialog(mw=self.mw, parent=self.mw)
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()
###################################################################### ######################################################################

View File

@ -47,6 +47,7 @@ from anki.collection import (
Config, Config,
OpChanges, OpChanges,
OpChangesWithCount, OpChangesWithCount,
OpChangesWithID,
ReviewUndo, ReviewUndo,
UndoResult, UndoResult,
UndoStatus, UndoStatus,
@ -102,7 +103,8 @@ class HasChangesProperty(Protocol):
# doesn't actually work for protobuf objects, so new protobuf objects will # doesn't actually work for protobuf objects, so new protobuf objects will
# either need to be added here, or cast at call time # either need to be added here, or cast at call time
ResultWithChanges = TypeVar( ResultWithChanges = TypeVar(
"ResultWithChanges", bound=Union[OpChanges, OpChangesWithCount, HasChangesProperty] "ResultWithChanges",
bound=Union[OpChanges, OpChangesWithCount, OpChangesWithID, HasChangesProperty],
) )
PerformOpOptionalSuccessCallback = Optional[Callable[[ResultWithChanges], Any]] PerformOpOptionalSuccessCallback = Optional[Callable[[ResultWithChanges], Any]]

View File

@ -4,21 +4,20 @@
from typing import List, Optional from typing import List, Optional
import aqt import aqt
from anki.errors import DeckIsFilteredError from anki.collection import OpChangesWithID
from aqt import gui_hooks from aqt import gui_hooks
from aqt.deck_ops import add_deck_dialog
from aqt.qt import * from aqt.qt import *
from aqt.utils import ( from aqt.utils import (
TR, TR,
HelpPage, HelpPage,
HelpPageArgument, HelpPageArgument,
disable_help_button, disable_help_button,
getOnlyText,
openHelp, openHelp,
restoreGeom, restoreGeom,
saveGeom, saveGeom,
shortcut, shortcut,
showInfo, showInfo,
showWarning,
tr, tr,
) )
@ -166,19 +165,14 @@ class StudyDeck(QDialog):
default = self.form.filter.text() default = self.form.filter.text()
else: else:
default = self.names[self.form.list.currentRow()] default = self.names[self.form.list.currentRow()]
n = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=default)
n = n.strip() def success(out: OpChangesWithID) -> None:
if n: deck = self.mw.col.decks.get(out.id)
try: self.name = deck["name"]
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 # make sure we clean up reset hook when manually exiting
gui_hooks.state_did_reset.remove(self.onReset) 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) 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; OpChanges changes = 2;
} }
message OpChangesWithID {
int64 id = 1;
OpChanges changes = 2;
}
// IDs used in RPC calls // IDs used in RPC calls
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -131,6 +136,7 @@ service SchedulingService {
} }
service DecksService { service DecksService {
rpc AddDeckLegacy(Json) returns (OpChangesWithID);
rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyIn) returns (DeckID); rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyIn) returns (DeckID);
rpc DeckTree(DeckTreeIn) returns (DeckTreeNode); rpc DeckTree(DeckTreeIn) returns (DeckTreeNode);
rpc DeckTreeLegacy(Empty) returns (Json); rpc DeckTreeLegacy(Empty) returns (Json);
@ -459,7 +465,7 @@ message CardRequirement {
message Deck { message Deck {
int64 id = 1; int64 id = 1;
string name = 2; string name = 2;
uint32 mtime_secs = 3; int64 mtime_secs = 3;
int32 usn = 4; int32 usn = 4;
DeckCommon common = 5; DeckCommon common = 5;
oneof kind { oneof kind {

View File

@ -10,6 +10,15 @@ use crate::{
pub(super) use pb::decks_service::Service as DecksService; pub(super) use pb::decks_service::Service as DecksService;
impl DecksService for Backend { 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> { fn add_or_update_deck_legacy(&self, input: pb::AddOrUpdateDeckLegacyIn) -> Result<pb::DeckId> {
self.with_col(|col| { self.with_col(|col| {
let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?; let schema11: DeckSchema11 = serde_json::from_slice(&input.deck)?;
@ -148,3 +157,40 @@ impl From<DeckID> for pb::DeckId {
pb::DeckId { did: did.0 } 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 } 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() 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 { DeckProto {
id: d.id.0, id: d.id.0,
name: d.name, name: d.name,
mtime_secs: d.mtime_secs.0 as u32, mtime_secs: d.mtime_secs.0,
usn: d.usn.0, usn: d.usn.0,
common: Some(d.common), common: Some(d.common),
kind: Some(d.kind.into()), kind: Some(d.kind.into()),

View File

@ -86,3 +86,15 @@ pub struct OpOutput<T> {
pub output: T, pub output: T,
pub changes: OpChanges, 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,
}
}
}