diff --git a/proto/backend.proto b/proto/backend.proto index cc8c4b88c..192450ce6 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -21,6 +21,10 @@ message Int32 { sint32 val = 1; } +message UInt32 { + uint32 val = 1; +} + message Int64 { int64 val = 1; } @@ -37,6 +41,19 @@ message Bool { bool val = 1; } +message NoteTypeID { + int64 ntid = 1; +} + +message NoteID { + int64 nid = 1; +} + +message CardID { + int64 cid = 1; +} + + // New style RPC definitions /////////////////////////////////////////////////////////// @@ -86,6 +103,22 @@ service BackendService { rpc NewDeckConfigLegacy (Empty) returns (Bytes); rpc RemoveDeckConfig (Int64) returns (Empty); + // cards + + rpc GetCard (CardID) returns (Card); + rpc UpdateCard (Card) returns (Empty); + rpc AddCard (Card) returns (CardID); + + // notes + + rpc NewNote (NoteTypeID) returns (Note); + rpc AddNote (AddNoteIn) returns (NoteID); + rpc UpdateNote (Note) returns (Empty); + rpc GetNote (NoteID) returns (Note); + rpc AddNoteTags (AddNoteTagsIn) returns (UInt32); + rpc UpdateNoteTags (UpdateNoteTagsIn) returns (UInt32); + rpc ClozeNumbersInNote (Note) returns (ClozeNumbersInNoteOut); + // misc rpc CheckDatabase (Empty) returns (CheckDatabaseOut); @@ -344,9 +377,6 @@ message BackendInput { Empty restore_trash = 35; OpenCollectionIn open_collection = 36; CloseCollectionIn close_collection = 37; - int64 get_card = 38; - Card update_card = 39; - Card add_card = 40; Empty abort_media_sync = 46; Empty before_upload = 47; RegisterTagsIn register_tags = 48; @@ -364,19 +394,12 @@ message BackendInput { Empty get_notetype_names_and_counts = 63; string get_notetype_id_by_name = 64; int64 remove_notetype = 65; - int64 new_note = 66; - AddNoteIn add_note = 67; - Note update_note = 68; - int64 get_note = 69; FieldNamesForNotesIn field_names_for_notes = 78; FindAndReplaceIn find_and_replace = 79; AfterNoteUpdatesIn after_note_updates = 80; - AddNoteTagsIn add_note_tags = 81; - UpdateNoteTagsIn update_note_tags = 82; int32 set_local_minutes_west = 83; Empty get_preferences = 84; Preferences set_preferences = 85; - Note cloze_numbers_in_note = 87; } } @@ -388,7 +411,6 @@ message BackendOutput { string studied_today = 32; string congrats_learn_msg = 33; Empty abort_media_sync = 46; - ClozeNumbersInNoteOut cloze_numbers_in_note = 87; // fallible commands string add_media_file = 26; @@ -396,9 +418,6 @@ message BackendOutput { Empty restore_trash = 35; Empty open_collection = 36; Empty close_collection = 37; - GetCardOut get_card = 38; - Empty update_card = 39; - int64 add_card = 40; Empty before_upload = 47; bool register_tags = 48; AllTagsOut all_tags = 50; @@ -414,15 +433,9 @@ message BackendOutput { NoteTypeUseCounts get_notetype_names_and_counts = 63; int64 get_notetype_id_by_name = 64; Empty remove_notetype = 65; - Note new_note = 66; - int64 add_note = 67; - Empty update_note = 68; - Note get_note = 69; FieldNamesForNotesOut field_names_for_notes = 78; uint32 find_and_replace = 79; Empty after_note_updates = 80; - uint32 add_note_tags = 81; - uint32 update_note_tags = 82; Empty set_local_minutes_west = 83; Preferences get_preferences = 84; Empty set_preferences = 85; @@ -698,11 +711,6 @@ message BuiltinSearchOrder { bool reverse = 2; } - -message GetCardOut { - Card card = 1; -} - message CloseCollectionIn { bool downgrade_to_schema11 = 1; } diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 56e015aa2..2c7c1c84c 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -467,4 +467,4 @@ and notes.mid = ? and cards.ord = ?""", def _availClozeOrds(self, m: NoteType, flds: str, allowEmpty: bool = True) -> List: print("_availClozeOrds() is deprecated; use note.cloze_numbers_in_fields()") note = anki.rsbackend.BackendNote(fields=[flds]) - return self.col.backend.cloze_numbers_in_note(note) + return list(self.col.backend.cloze_numbers_in_note(note)) diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index f720f9371..4a91b762e 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Any, List, Optional, Tuple +from typing import Any, List, Optional, Sequence, Tuple import anki # pylint: disable=unused-import from anki import hooks @@ -82,7 +82,7 @@ class Note: _model = property(model) - def cloze_numbers_in_fields(self) -> List[int]: + def cloze_numbers_in_fields(self) -> Sequence[int]: return self.col.backend.cloze_numbers_in_note(self.to_backend_note()) # Dict interface diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 409302ad2..332d16964 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -306,20 +306,6 @@ class RustBackend: def _db_command(self, input: Dict[str, Any]) -> Any: return orjson.loads(self._backend.db_command(orjson.dumps(input))) - def get_card(self, cid: int) -> Optional[pb.Card]: - output = self._run_command(pb.BackendInput(get_card=cid)).get_card - if output.HasField("card"): - return output.card - else: - return None - - def update_card(self, card: BackendCard) -> None: - self._run_command(pb.BackendInput(update_card=card)) - - # returns the new card id - def add_card(self, card: BackendCard) -> int: - return self._run_command(pb.BackendInput(add_card=card)).add_card - def abort_media_sync(self): self._run_command(pb.BackendInput(abort_media_sync=pb.Empty())) @@ -442,23 +428,6 @@ class RustBackend: def remove_notetype(self, ntid: int) -> None: self._run_command(pb.BackendInput(remove_notetype=ntid), release_gil=True) - def new_note(self, ntid: int) -> BackendNote: - return self._run_command(pb.BackendInput(new_note=ntid)).new_note - - def add_note(self, note: BackendNote, deck_id: int) -> int: - return self._run_command( - pb.BackendInput(add_note=pb.AddNoteIn(note=note, deck_id=deck_id)) - ).add_note - - def update_note(self, note: BackendNote) -> None: - self._run_command(pb.BackendInput(update_note=note)) - - def get_note(self, nid) -> Optional[BackendNote]: - try: - return self._run_command(pb.BackendInput(get_note=nid)).get_note - except NotFoundError: - return None - def field_names_for_note_ids(self, nids: List[int]) -> Sequence[str]: return self._run_command( pb.BackendInput(field_names_for_notes=pb.FieldNamesForNotesIn(nids=nids)), @@ -502,22 +471,6 @@ class RustBackend: release_gil=True, ) - def add_note_tags(self, nids: List[int], tags: str) -> int: - return self._run_command( - pb.BackendInput(add_note_tags=pb.AddNoteTagsIn(nids=nids, tags=tags)) - ).add_note_tags - - def update_note_tags( - self, nids: List[int], tags: str, replacement: str, regex: bool - ) -> int: - return self._run_command( - pb.BackendInput( - update_note_tags=pb.UpdateNoteTagsIn( - nids=nids, tags=tags, replacement=replacement, regex=regex - ) - ) - ).update_note_tags - def set_local_minutes_west(self, mins: int) -> None: self._run_command(pb.BackendInput(set_local_minutes_west=mins)) @@ -529,13 +482,6 @@ class RustBackend: def set_preferences(self, prefs: pb.Preferences) -> None: self._run_command(pb.BackendInput(set_preferences=prefs)) - def cloze_numbers_in_note(self, note: pb.Note) -> List[int]: - return list( - self._run_command( - pb.BackendInput(cloze_numbers_in_note=note) - ).cloze_numbers_in_note.numbers - ) - def _run_command2(self, method: int, input: Any) -> bytes: input_bytes = input.SerializeToString() try: @@ -731,10 +677,70 @@ class RustBackend: output.ParseFromString(self._run_command2(27, input)) return output + def get_card(self, cid: int) -> pb.Card: + input = pb.CardID(cid=cid) + output = pb.Card() + output.ParseFromString(self._run_command2(28, input)) + return output + + def update_card(self, input: pb.Card) -> pb.Empty: + output = pb.Empty() + output.ParseFromString(self._run_command2(29, input)) + return output + + def add_card(self, input: pb.Card) -> int: + output = pb.CardID() + output.ParseFromString(self._run_command2(30, input)) + return output.cid + + def new_note(self, ntid: int) -> pb.Note: + input = pb.NoteTypeID(ntid=ntid) + output = pb.Note() + output.ParseFromString(self._run_command2(31, input)) + return output + + def add_note(self, note: pb.Note, deck_id: int) -> int: + input = pb.AddNoteIn(note=note, deck_id=deck_id) + output = pb.NoteID() + output.ParseFromString(self._run_command2(32, input)) + return output.nid + + def update_note(self, input: pb.Note) -> pb.Empty: + output = pb.Empty() + output.ParseFromString(self._run_command2(33, input)) + return output + + def get_note(self, nid: int) -> pb.Note: + input = pb.NoteID(nid=nid) + output = pb.Note() + output.ParseFromString(self._run_command2(34, input)) + return output + + def add_note_tags(self, nids: Sequence[int], tags: str) -> int: + input = pb.AddNoteTagsIn(nids=nids, tags=tags) + output = pb.UInt32() + output.ParseFromString(self._run_command2(35, input)) + return output.val + + def update_note_tags( + self, nids: Sequence[int], tags: str, replacement: str, regex: bool + ) -> int: + input = pb.UpdateNoteTagsIn( + nids=nids, tags=tags, replacement=replacement, regex=regex + ) + output = pb.UInt32() + output.ParseFromString(self._run_command2(36, input)) + return output.val + + def cloze_numbers_in_note(self, input: pb.Note) -> Sequence[int]: + output = pb.ClozeNumbersInNoteOut() + output.ParseFromString(self._run_command2(37, input)) + return output.numbers + def check_database(self) -> Sequence[str]: input = pb.Empty() output = pb.CheckDatabaseOut() - output.ParseFromString(self._run_command2(28, input)) + output.ParseFromString(self._run_command2(38, input)) return output.problems # @@AUTOGEN@@ diff --git a/pylib/tests/test_models.py b/pylib/tests/test_models.py index 7246295aa..0b4c7148d 100644 --- a/pylib/tests/test_models.py +++ b/pylib/tests/test_models.py @@ -2,6 +2,7 @@ import time from anki.consts import MODEL_CLOZE +from anki.rsbackend import NotFoundError from anki.utils import isWin, stripHTML from tests.shared import getEmptyCol @@ -323,7 +324,7 @@ def test_modelChange(): try: c1.load() assert 0 - except AssertionError: + except NotFoundError: pass # but we have two cards, as a new one was generated assert len(f.cards()) == 2 diff --git a/pylib/tools/genbackend.py b/pylib/tools/genbackend.py index 47c4c1428..969d7bebb 100755 --- a/pylib/tools/genbackend.py +++ b/pylib/tools/genbackend.py @@ -77,8 +77,16 @@ def get_input_assign(msg): def render_method(method, idx): - input_args = get_input_args(method.input_type) - input_assign = get_input_assign(method.input_type) + input_name = method.input_type.name + if input_name.endswith("In") or len(method.input_type.fields) < 2: + input_args = get_input_args(method.input_type) + input_assign = get_input_assign(method.input_type) + input_assign_outer = ( + f"input = pb.{method.input_type.name}({input_assign})\n " + ) + else: + input_args = f"self, input: pb.{input_name}" + input_assign_outer = "" name = fix_snakecase(stringcase.snakecase(method.name)) if len(method.output_type.fields) == 1: # unwrap single return arg @@ -90,8 +98,7 @@ def render_method(method, idx): return_type = f"pb.{method.output_type.name}" return f"""\ def {name}({input_args}) -> {return_type}: - input = pb.{method.input_type.name}({input_assign}) - output = pb.{method.output_type.name}() + {input_assign_outer}output = pb.{method.output_type.name}() output.ParseFromString(self._run_command2({idx+1}, input)) return output{single_field} """ diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 3544be70a..9b272459d 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -166,12 +166,36 @@ impl From for pb::Int64 { } } +impl From for pb::UInt32 { + fn from(val: u32) -> Self { + pb::UInt32 { val } + } +} + impl From<()> for pb::Empty { fn from(_val: ()) -> Self { pb::Empty {} } } +impl From for CardID { + fn from(cid: pb::CardId) -> Self { + CardID(cid.cid) + } +} + +impl From for NoteID { + fn from(nid: pb::NoteId) -> Self { + NoteID(nid.nid) + } +} + +impl From for NoteTypeID { + fn from(ntid: pb::NoteTypeId) -> Self { + NoteTypeID(ntid.ntid) + } +} + impl BackendService for Backend { // card rendering @@ -486,6 +510,106 @@ impl BackendService for Backend { .map(Into::into) } + // cards + //------------------------------------------------------------------- + + fn get_card(&mut self, input: pb::CardId) -> BackendResult { + self.with_col(|col| { + col.storage + .get_card(input.into()) + .and_then(|opt| opt.ok_or(AnkiError::NotFound)) + .map(card_to_pb) + }) + } + + fn update_card(&mut self, input: pb::Card) -> BackendResult { + let mut card = pbcard_to_native(input)?; + self.with_col(|col| { + col.transact(None, |ctx| { + let orig = ctx + .storage + .get_card(card.id)? + .ok_or_else(|| AnkiError::invalid_input("missing card"))?; + ctx.update_card(&mut card, &orig) + }) + }) + .map(Into::into) + } + + fn add_card(&mut self, input: pb::Card) -> BackendResult { + let mut card = pbcard_to_native(input)?; + self.with_col(|col| col.transact(None, |ctx| ctx.add_card(&mut card)))?; + Ok(pb::CardId { cid: card.id.0 }) + } + + // notes + //------------------------------------------------------------------- + + fn new_note(&mut self, input: pb::NoteTypeId) -> BackendResult { + self.with_col(|col| { + let nt = col.get_notetype(input.into())?.ok_or(AnkiError::NotFound)?; + Ok(nt.new_note().into()) + }) + } + + fn add_note(&mut self, input: pb::AddNoteIn) -> BackendResult { + self.with_col(|col| { + let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into(); + col.add_note(&mut note, DeckID(input.deck_id)) + .map(|_| pb::NoteId { nid: note.id.0 }) + }) + } + + fn update_note(&mut self, input: pb::Note) -> BackendResult { + self.with_col(|col| { + let mut note: Note = input.into(); + col.update_note(&mut note) + }) + .map(Into::into) + } + + fn get_note(&mut self, input: pb::NoteId) -> BackendResult { + self.with_col(|col| { + col.storage + .get_note(input.into())? + .ok_or(AnkiError::NotFound) + .map(Into::into) + }) + } + + fn add_note_tags(&mut self, input: pb::AddNoteTagsIn) -> BackendResult { + self.with_col(|col| { + col.add_tags_for_notes(&to_nids(input.nids), &input.tags) + .map(|n| n as u32) + }) + .map(Into::into) + } + + fn update_note_tags(&mut self, input: pb::UpdateNoteTagsIn) -> BackendResult { + self.with_col(|col| { + col.replace_tags_for_notes( + &to_nids(input.nids), + &input.tags, + &input.replacement, + input.regex, + ) + .map(|n| (n as u32).into()) + }) + } + + fn cloze_numbers_in_note( + &mut self, + note: pb::Note, + ) -> BackendResult { + let mut set = HashSet::with_capacity(4); + for field in ¬e.fields { + add_cloze_numbers_in_string(field, &mut set); + } + Ok(pb::ClozeNumbersInNoteOut { + numbers: set.into_iter().map(|n| n as u32).collect(), + }) + } + // media //------------------------------------------------------------------- @@ -669,12 +793,6 @@ impl Backend { self.close_collection(input.downgrade_to_schema11)?; OValue::CloseCollection(Empty {}) } - Value::GetCard(cid) => OValue::GetCard(self.get_card(cid)?), - Value::UpdateCard(card) => { - self.update_card(card)?; - OValue::UpdateCard(pb::Empty {}) - } - Value::AddCard(card) => OValue::AddCard(self.add_card(card)?), Value::AbortMediaSync(_) => { self.abort_media_sync(); OValue::AbortMediaSync(pb::Empty {}) @@ -721,13 +839,6 @@ impl Backend { self.remove_notetype(id)?; OValue::RemoveNotetype(pb::Empty {}) } - Value::NewNote(ntid) => OValue::NewNote(self.new_note(ntid)?), - Value::AddNote(input) => OValue::AddNote(self.add_note(input)?), - Value::UpdateNote(note) => { - self.update_note(note)?; - OValue::UpdateNote(pb::Empty {}) - } - Value::GetNote(nid) => OValue::GetNote(self.get_note(nid)?), Value::FieldNamesForNotes(input) => { OValue::FieldNamesForNotes(self.field_names_for_notes(input)?) } @@ -735,8 +846,6 @@ impl Backend { Value::AfterNoteUpdates(input) => { OValue::AfterNoteUpdates(self.after_note_updates(input)?) } - Value::AddNoteTags(input) => OValue::AddNoteTags(self.add_note_tags(input)?), - Value::UpdateNoteTags(input) => OValue::UpdateNoteTags(self.update_note_tags(input)?), Value::SetLocalMinutesWest(mins) => OValue::SetLocalMinutesWest({ self.set_local_mins_west(mins)?; pb::Empty {} @@ -746,9 +855,6 @@ impl Backend { self.set_preferences(prefs)?; pb::Empty {} }), - Value::ClozeNumbersInNote(note) => { - OValue::ClozeNumbersInNote(self.cloze_numbers_in_note(note)) - } }) } @@ -924,32 +1030,6 @@ impl Backend { self.with_col(|col| db_command_bytes(&col.storage, input)) } - fn get_card(&self, cid: i64) -> Result { - let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?; - Ok(pb::GetCardOut { - card: card.map(card_to_pb), - }) - } - - fn update_card(&self, pbcard: pb::Card) -> Result<()> { - let mut card = pbcard_to_native(pbcard)?; - self.with_col(|col| { - col.transact(None, |ctx| { - let orig = ctx - .storage - .get_card(card.id)? - .ok_or_else(|| AnkiError::invalid_input("missing card"))?; - ctx.update_card(&mut card, &orig) - }) - }) - } - - fn add_card(&self, pbcard: pb::Card) -> Result { - let mut card = pbcard_to_native(pbcard)?; - self.with_col(|col| col.transact(None, |ctx| ctx.add_card(&mut card)))?; - Ok(card.id.0) - } - fn before_upload(&self) -> Result<()> { self.with_col(|col| col.before_upload()) } @@ -1104,39 +1184,6 @@ impl Backend { self.with_col(|col| col.remove_notetype(NoteTypeID(id))) } - fn new_note(&self, ntid: i64) -> Result { - self.with_col(|col| { - let nt = col - .get_notetype(NoteTypeID(ntid))? - .ok_or(AnkiError::NotFound)?; - Ok(nt.new_note().into()) - }) - } - - fn add_note(&self, input: pb::AddNoteIn) -> Result { - self.with_col(|col| { - let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into(); - col.add_note(&mut note, DeckID(input.deck_id)) - .map(|_| note.id.0) - }) - } - - fn update_note(&self, pbnote: pb::Note) -> Result<()> { - self.with_col(|col| { - let mut note: Note = pbnote.into(); - col.update_note(&mut note) - }) - } - - fn get_note(&self, nid: i64) -> Result { - self.with_col(|col| { - col.storage - .get_note(NoteID(nid))? - .ok_or(AnkiError::NotFound) - .map(Into::into) - }) - } - fn field_names_for_notes( &self, input: pb::FieldNamesForNotesIn, @@ -1184,25 +1231,6 @@ impl Backend { }) } - fn add_note_tags(&self, input: pb::AddNoteTagsIn) -> Result { - self.with_col(|col| { - col.add_tags_for_notes(&to_nids(input.nids), &input.tags) - .map(|n| n as u32) - }) - } - - fn update_note_tags(&self, input: pb::UpdateNoteTagsIn) -> Result { - self.with_col(|col| { - col.replace_tags_for_notes( - &to_nids(input.nids), - &input.tags, - &input.replacement, - input.regex, - ) - .map(|n| n as u32) - }) - } - fn set_local_mins_west(&self, mins: i32) -> Result<()> { self.with_col(|col| col.transact(None, |col| col.set_local_mins_west(mins))) } @@ -1215,16 +1243,6 @@ impl Backend { self.with_col(|col| col.transact(None, |col| col.set_preferences(prefs))) } - fn cloze_numbers_in_note(&self, note: pb::Note) -> pb::ClozeNumbersInNoteOut { - let mut set = HashSet::with_capacity(4); - for field in ¬e.fields { - add_cloze_numbers_in_string(field, &mut set); - } - pb::ClozeNumbersInNoteOut { - numbers: set.into_iter().map(|n| n as u32).collect(), - } - } - fn get_stock_notetype_legacy(&self, kind: i32) -> Result> { // fixme: use individual functions instead of full vec let mut all = all_stock_notetypes(&self.i18n);