From 4bf8175bcbb59a08d282c7f5edb16c6998dc1ecb Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 23 May 2020 21:34:19 +1000 Subject: [PATCH] migrate more scheduling/media/etc almost there --- proto/backend.proto | 49 ++---- pylib/anki/collection.py | 2 +- pylib/anki/find.py | 2 +- pylib/anki/media.py | 2 +- pylib/anki/rsbackend.py | 264 +++++++++++++++--------------- pylib/anki/schedv2.py | 2 +- rslib/src/backend/mod.rs | 337 +++++++++++++++++++-------------------- rspy/src/lib.rs | 11 ++ 8 files changed, 328 insertions(+), 341 deletions(-) diff --git a/proto/backend.proto b/proto/backend.proto index d35486b11..53fb173d5 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -81,17 +81,23 @@ service BackendService { rpc SearchCards (SearchCardsIn) returns (SearchCardsOut); rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut); + rpc FindAndReplace (FindAndReplaceIn) returns (UInt32); // scheduling rpc LocalMinutesWest (Int64) returns (Int32); + rpc SetLocalMinutesWest (Int32) returns (Empty); rpc SchedTimingToday (Empty) returns (SchedTimingTodayOut); + rpc StudiedToday (StudiedTodayIn) returns (String); + rpc CongratsLearnMessage (CongratsLearnMessageIn) returns (String); // media rpc CheckMedia (Empty) returns (CheckMediaOut); - rpc SyncMedia (SyncMediaIn) returns (Empty); rpc TrashMediaFiles (TrashMediaFilesIn) returns (Empty); + rpc AddMediaFile (AddMediaFileIn) returns (String); + rpc EmptyTrash (Empty) returns (Empty); + rpc RestoreTrash (Empty) returns (Empty); // decks @@ -141,10 +147,17 @@ service BackendService { rpc GetNotetypeIDByName (String) returns (NoteTypeID); rpc RemoveNotetype (NoteTypeID) returns (Empty); - // misc + // collection + rpc OpenCollection (OpenCollectionIn) returns (Empty); + rpc CloseCollection (CloseCollectionIn) returns (Empty); rpc CheckDatabase (Empty) returns (CheckDatabaseOut); + // sync + + rpc SyncMedia (SyncMediaIn) returns (Empty); + rpc AbortMediaSync (Empty) returns (Empty); + rpc BeforeUpload (Empty) returns (Empty); } // Protobuf stored in .anki2 files @@ -390,20 +403,8 @@ message I18nBackendInit { message BackendInput { oneof value { - AddMediaFileIn add_media_file = 26; - Empty empty_trash = 34; - Empty restore_trash = 35; - Empty abort_media_sync = 46; - TranslateStringIn translate_string = 30; FormatTimeSpanIn format_time_span = 31; - StudiedTodayIn studied_today = 32; - CongratsLearnMsgIn congrats_learn_msg = 33; - - OpenCollectionIn open_collection = 36; - CloseCollectionIn close_collection = 37; - - Empty before_upload = 47; RegisterTagsIn register_tags = 48; Empty all_tags = 50; @@ -414,9 +415,6 @@ message BackendInput { bytes set_all_config = 54; Empty get_all_config = 55; - FindAndReplaceIn find_and_replace = 79; - - int32 set_local_minutes_west = 83; Empty get_preferences = 84; Preferences set_preferences = 85; } @@ -424,31 +422,18 @@ message BackendInput { message BackendOutput { oneof value { - // infallible commands string translate_string = 30; string format_time_span = 31; - string studied_today = 32; - string congrats_learn_msg = 33; - Empty abort_media_sync = 46; - // fallible commands - string add_media_file = 26; - Empty empty_trash = 34; - Empty restore_trash = 35; - Empty open_collection = 36; - Empty close_collection = 37; - Empty before_upload = 47; bool register_tags = 48; AllTagsOut all_tags = 50; GetChangedTagsOut get_changed_tags = 51; + bytes get_config_json = 52; Empty set_config_json = 53; Empty set_all_config = 54; bytes get_all_config = 55; - uint32 find_and_replace = 79; - - Empty set_local_minutes_west = 83; Preferences get_preferences = 84; Empty set_preferences = 85; @@ -663,7 +648,7 @@ message StudiedTodayIn { double seconds = 2; } -message CongratsLearnMsgIn { +message CongratsLearnMessageIn { float next_due = 1; uint32 remaining = 2; } diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index c01579742..2c37f805a 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -209,7 +209,7 @@ class Collection: else: self.db.rollback() self.models._clear_cache() - self.backend.close_collection(downgrade=downgrade) + self.backend.close_collection(downgrade_to_schema11=downgrade) self.db = None self.media.close() self._closeLog() diff --git a/pylib/anki/find.py b/pylib/anki/find.py index 5eb751b9f..11cfc2e30 100644 --- a/pylib/anki/find.py +++ b/pylib/anki/find.py @@ -38,7 +38,7 @@ def findReplace( fold: bool = True, ) -> int: "Find and replace fields in a note. Returns changed note count." - return col.backend.find_and_replace(nids, src, dst, regex, fold, field) + return col.backend.find_and_replace(nids, src, dst, regex, not fold, field) def fieldNamesForNotes(col: Collection, nids: List[int]) -> List[str]: diff --git a/pylib/anki/media.py b/pylib/anki/media.py index da6fcc934..4dc7eaf07 100644 --- a/pylib/anki/media.py +++ b/pylib/anki/media.py @@ -96,7 +96,7 @@ class MediaManager: """Write the file to the media folder, renaming if not unique. Returns possibly-renamed filename.""" - return self.col.backend.add_file_to_media_folder(desired_fname, data) + return self.col.backend.add_media_file(desired_fname, data) def add_extension_based_on_mime(self, fname: str, content_type: str) -> str: "If jpg or png mime, add .png/.jpg if missing extension." diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 5550c6ce0..987c478e6 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -216,36 +216,6 @@ class RustBackend: else: return output - def open_collection( - self, col_path: str, media_folder_path: str, media_db_path: str, log_path: str - ): - self._run_command( - pb.BackendInput( - open_collection=pb.OpenCollectionIn( - collection_path=col_path, - media_folder_path=media_folder_path, - media_db_path=media_db_path, - log_path=log_path, - ) - ), - release_gil=True, - ) - - def close_collection(self, downgrade=True): - self._run_command( - pb.BackendInput( - close_collection=pb.CloseCollectionIn(downgrade_to_schema11=downgrade) - ), - release_gil=True, - ) - - def add_file_to_media_folder(self, desired_name: str, data: bytes) -> str: - return self._run_command( - pb.BackendInput( - add_media_file=pb.AddMediaFileIn(desired_name=desired_name, data=data) - ) - ).add_media_file - def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str: return self._run_command( pb.BackendInput(translate_string=translate_string_in(key, **kwargs)) @@ -262,28 +232,6 @@ class RustBackend: ) ).format_time_span - def studied_today(self, cards: int, seconds: float) -> str: - return self._run_command( - pb.BackendInput( - studied_today=pb.StudiedTodayIn(cards=cards, seconds=seconds) - ) - ).studied_today - - def learning_congrats_msg(self, next_due: float, remaining: int) -> str: - return self._run_command( - pb.BackendInput( - congrats_learn_msg=pb.CongratsLearnMsgIn( - next_due=next_due, remaining=remaining - ) - ) - ).congrats_learn_msg - - def empty_trash(self): - self._run_command(pb.BackendInput(empty_trash=pb.Empty())) - - def restore_trash(self): - self._run_command(pb.BackendInput(restore_trash=pb.Empty())) - def db_query( self, sql: str, args: Sequence[ValueForDB], first_row_only: bool ) -> List[DBRow]: @@ -306,9 +254,6 @@ class RustBackend: def _db_command(self, input: Dict[str, Any]) -> Any: return orjson.loads(self._backend.db_command(orjson.dumps(input))) - def abort_media_sync(self): - self._run_command(pb.BackendInput(abort_media_sync=pb.Empty())) - def all_tags(self) -> Iterable[TagUsnTuple]: return self._run_command(pb.BackendInput(all_tags=pb.Empty())).all_tags.tags @@ -331,9 +276,6 @@ class RustBackend: ) ).register_tags - def before_upload(self): - self._run_command(pb.BackendInput(before_upload=pb.Empty())) - def get_changed_tags(self, usn: int) -> List[str]: return list( self._run_command( @@ -370,32 +312,6 @@ class RustBackend: def set_all_config(self, conf: Dict[str, Any]): self._run_command(pb.BackendInput(set_all_config=orjson.dumps(conf))) - def find_and_replace( - self, - nids: List[int], - search: str, - repl: str, - re: bool, - nocase: bool, - field_name: Optional[str], - ) -> int: - return self._run_command( - pb.BackendInput( - find_and_replace=pb.FindAndReplaceIn( - nids=nids, - search=search, - replacement=repl, - regex=re, - match_case=not nocase, - field_name=field_name, - ) - ), - release_gil=True, - ).find_and_replace - - def set_local_minutes_west(self, mins: int) -> None: - self._run_command(pb.BackendInput(set_local_minutes_west=mins)) - def get_preferences(self) -> pb.Preferences: return self._run_command( pb.BackendInput(get_preferences=pb.Empty()) @@ -473,34 +389,85 @@ class RustBackend: output.ParseFromString(self._run_command2(8, input)) return output.note_ids + def find_and_replace( + self, + nids: Sequence[int], + search: str, + replacement: str, + regex: bool, + match_case: bool, + field_name: str, + ) -> int: + input = pb.FindAndReplaceIn( + nids=nids, + search=search, + replacement=replacement, + regex=regex, + match_case=match_case, + field_name=field_name, + ) + output = pb.UInt32() + output.ParseFromString(self._run_command2(9, input)) + return output.val + def local_minutes_west(self, val: int) -> int: input = pb.Int64(val=val) output = pb.Int32() - output.ParseFromString(self._run_command2(9, input)) + output.ParseFromString(self._run_command2(10, input)) return output.val + def set_local_minutes_west(self, val: int) -> pb.Empty: + input = pb.Int32(val=val) + output = pb.Empty() + output.ParseFromString(self._run_command2(11, input)) + return output + def sched_timing_today(self) -> pb.SchedTimingTodayOut: input = pb.Empty() output = pb.SchedTimingTodayOut() - output.ParseFromString(self._run_command2(10, input)) + output.ParseFromString(self._run_command2(12, input)) return output + def studied_today(self, cards: int, seconds: float) -> str: + input = pb.StudiedTodayIn(cards=cards, seconds=seconds) + output = pb.String() + output.ParseFromString(self._run_command2(13, input)) + return output.val + + def congrats_learn_message(self, next_due: float, remaining: int) -> str: + input = pb.CongratsLearnMessageIn(next_due=next_due, remaining=remaining) + output = pb.String() + output.ParseFromString(self._run_command2(14, input)) + return output.val + def check_media(self) -> pb.CheckMediaOut: input = pb.Empty() output = pb.CheckMediaOut() - output.ParseFromString(self._run_command2(11, input)) - return output - - def sync_media(self, hkey: str, endpoint: str) -> pb.Empty: - input = pb.SyncMediaIn(hkey=hkey, endpoint=endpoint) - output = pb.Empty() - output.ParseFromString(self._run_command2(12, input)) + output.ParseFromString(self._run_command2(15, input)) return output def trash_media_files(self, fnames: Sequence[str]) -> pb.Empty: input = pb.TrashMediaFilesIn(fnames=fnames) output = pb.Empty() - output.ParseFromString(self._run_command2(13, input)) + output.ParseFromString(self._run_command2(16, input)) + return output + + def add_media_file(self, desired_name: str, data: bytes) -> str: + input = pb.AddMediaFileIn(desired_name=desired_name, data=data) + output = pb.String() + output.ParseFromString(self._run_command2(17, input)) + return output.val + + def empty_trash(self) -> pb.Empty: + input = pb.Empty() + output = pb.Empty() + output.ParseFromString(self._run_command2(18, input)) + return output + + def restore_trash(self) -> pb.Empty: + input = pb.Empty() + output = pb.Empty() + output.ParseFromString(self._run_command2(19, input)) return output def add_or_update_deck_legacy( @@ -510,37 +477,37 @@ class RustBackend: deck=deck, preserve_usn_and_mtime=preserve_usn_and_mtime ) output = pb.DeckID() - output.ParseFromString(self._run_command2(14, input)) + output.ParseFromString(self._run_command2(20, input)) return output.did def deck_tree(self, include_counts: bool, top_deck_id: int) -> pb.DeckTreeNode: input = pb.DeckTreeIn(include_counts=include_counts, top_deck_id=top_deck_id) output = pb.DeckTreeNode() - output.ParseFromString(self._run_command2(15, input)) + output.ParseFromString(self._run_command2(21, input)) return output def deck_tree_legacy(self) -> bytes: input = pb.Empty() output = pb.Json() - output.ParseFromString(self._run_command2(16, input)) + output.ParseFromString(self._run_command2(22, input)) return output.json def get_all_decks_legacy(self) -> bytes: input = pb.Empty() output = pb.Json() - output.ParseFromString(self._run_command2(17, input)) + output.ParseFromString(self._run_command2(23, input)) return output.json def get_deck_id_by_name(self, val: str) -> int: input = pb.String(val=val) output = pb.DeckID() - output.ParseFromString(self._run_command2(18, input)) + output.ParseFromString(self._run_command2(24, input)) return output.did def get_deck_legacy(self, did: int) -> bytes: input = pb.DeckID(did=did) output = pb.Json() - output.ParseFromString(self._run_command2(19, input)) + output.ParseFromString(self._run_command2(25, input)) return output.json def get_deck_names( @@ -550,19 +517,19 @@ class RustBackend: skip_empty_default=skip_empty_default, include_filtered=include_filtered ) output = pb.DeckNames() - output.ParseFromString(self._run_command2(20, input)) + output.ParseFromString(self._run_command2(26, input)) return output.entries def new_deck_legacy(self, val: bool) -> bytes: input = pb.Bool(val=val) output = pb.Json() - output.ParseFromString(self._run_command2(21, input)) + output.ParseFromString(self._run_command2(27, input)) return output.json def remove_deck(self, did: int) -> pb.Empty: input = pb.DeckID(did=did) output = pb.Empty() - output.ParseFromString(self._run_command2(22, input)) + output.ParseFromString(self._run_command2(28, input)) return output def add_or_update_deck_config_legacy( @@ -572,76 +539,76 @@ class RustBackend: config=config, preserve_usn_and_mtime=preserve_usn_and_mtime ) output = pb.DeckConfigID() - output.ParseFromString(self._run_command2(23, input)) + output.ParseFromString(self._run_command2(29, input)) return output.dcid def all_deck_config_legacy(self) -> bytes: input = pb.Empty() output = pb.Json() - output.ParseFromString(self._run_command2(24, input)) + output.ParseFromString(self._run_command2(30, input)) return output.json def get_deck_config_legacy(self, dcid: int) -> bytes: input = pb.DeckConfigID(dcid=dcid) output = pb.Json() - output.ParseFromString(self._run_command2(25, input)) + output.ParseFromString(self._run_command2(31, input)) return output.json def new_deck_config_legacy(self) -> bytes: input = pb.Empty() output = pb.Json() - output.ParseFromString(self._run_command2(26, input)) + output.ParseFromString(self._run_command2(32, input)) return output.json def remove_deck_config(self, dcid: int) -> pb.Empty: input = pb.DeckConfigID(dcid=dcid) output = pb.Empty() - output.ParseFromString(self._run_command2(27, input)) + output.ParseFromString(self._run_command2(33, 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)) + output.ParseFromString(self._run_command2(34, input)) return output def update_card(self, input: pb.Card) -> pb.Empty: output = pb.Empty() - output.ParseFromString(self._run_command2(29, input)) + output.ParseFromString(self._run_command2(35, input)) return output def add_card(self, input: pb.Card) -> int: output = pb.CardID() - output.ParseFromString(self._run_command2(30, input)) + output.ParseFromString(self._run_command2(36, 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)) + output.ParseFromString(self._run_command2(37, 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)) + output.ParseFromString(self._run_command2(38, input)) return output.nid def update_note(self, input: pb.Note) -> pb.Empty: output = pb.Empty() - output.ParseFromString(self._run_command2(33, input)) + output.ParseFromString(self._run_command2(39, 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)) + output.ParseFromString(self._run_command2(40, 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)) + output.ParseFromString(self._run_command2(41, input)) return output.val def update_note_tags( @@ -651,12 +618,12 @@ class RustBackend: nids=nids, tags=tags, replacement=replacement, regex=regex ) output = pb.UInt32() - output.ParseFromString(self._run_command2(36, input)) + output.ParseFromString(self._run_command2(42, 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)) + output.ParseFromString(self._run_command2(43, input)) return output.numbers def after_note_updates( @@ -668,13 +635,13 @@ class RustBackend: generate_cards=generate_cards, ) output = pb.Empty() - output.ParseFromString(self._run_command2(38, input)) + output.ParseFromString(self._run_command2(44, input)) return output def field_names_for_notes(self, nids: Sequence[int]) -> Sequence[str]: input = pb.FieldNamesForNotesIn(nids=nids) output = pb.FieldNamesForNotesOut() - output.ParseFromString(self._run_command2(39, input)) + output.ParseFromString(self._run_command2(45, input)) return output.fields def add_or_update_notetype(self, json: bytes, preserve_usn_and_mtime: bool) -> int: @@ -682,51 +649,92 @@ class RustBackend: json=json, preserve_usn_and_mtime=preserve_usn_and_mtime ) output = pb.NoteTypeID() - output.ParseFromString(self._run_command2(40, input)) + output.ParseFromString(self._run_command2(46, input)) return output.ntid def get_stock_notetype_legacy(self, kind: pb.StockNoteType) -> bytes: input = pb.GetStockNotetypeIn(kind=kind) output = pb.Json() - output.ParseFromString(self._run_command2(41, input)) + output.ParseFromString(self._run_command2(47, input)) return output.json def get_notetype_legacy(self, ntid: int) -> bytes: input = pb.NoteTypeID(ntid=ntid) output = pb.Json() - output.ParseFromString(self._run_command2(42, input)) + output.ParseFromString(self._run_command2(48, input)) return output.json def get_notetype_names(self) -> Sequence[pb.NoteTypeNameID]: input = pb.Empty() output = pb.NoteTypeNames() - output.ParseFromString(self._run_command2(43, input)) + output.ParseFromString(self._run_command2(49, input)) return output.entries def get_notetype_names_and_counts(self) -> Sequence[pb.NoteTypeNameIDUseCount]: input = pb.Empty() output = pb.NoteTypeUseCounts() - output.ParseFromString(self._run_command2(44, input)) + output.ParseFromString(self._run_command2(50, input)) return output.entries def get_notetype_id_by_name(self, val: str) -> int: input = pb.String(val=val) output = pb.NoteTypeID() - output.ParseFromString(self._run_command2(45, input)) + output.ParseFromString(self._run_command2(51, input)) return output.ntid def remove_notetype(self, ntid: int) -> pb.Empty: input = pb.NoteTypeID(ntid=ntid) output = pb.Empty() - output.ParseFromString(self._run_command2(46, input)) + output.ParseFromString(self._run_command2(52, input)) + return output + + def open_collection( + self, + collection_path: str, + media_folder_path: str, + media_db_path: str, + log_path: str, + ) -> pb.Empty: + input = pb.OpenCollectionIn( + collection_path=collection_path, + media_folder_path=media_folder_path, + media_db_path=media_db_path, + log_path=log_path, + ) + output = pb.Empty() + output.ParseFromString(self._run_command2(53, input)) + return output + + def close_collection(self, downgrade_to_schema11: bool) -> pb.Empty: + input = pb.CloseCollectionIn(downgrade_to_schema11=downgrade_to_schema11) + output = pb.Empty() + output.ParseFromString(self._run_command2(54, input)) return output def check_database(self) -> Sequence[str]: input = pb.Empty() output = pb.CheckDatabaseOut() - output.ParseFromString(self._run_command2(47, input)) + output.ParseFromString(self._run_command2(55, input)) return output.problems + def sync_media(self, hkey: str, endpoint: str) -> pb.Empty: + input = pb.SyncMediaIn(hkey=hkey, endpoint=endpoint) + output = pb.Empty() + output.ParseFromString(self._run_command2(56, input)) + return output + + def abort_media_sync(self) -> pb.Empty: + input = pb.Empty() + output = pb.Empty() + output.ParseFromString(self._run_command2(57, input)) + return output + + def before_upload(self) -> pb.Empty: + input = pb.Empty() + output = pb.Empty() + output.ParseFromString(self._run_command2(58, input)) + return output + # @@AUTOGEN@@ diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index 21446671e..6c4c216d0 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -1270,7 +1270,7 @@ from cards where did in {dids} and queue = {QUEUE_TYPE_LRN} remaining = remaining or 0 if next and next < self.dayCutoff: next -= intTime() - self.col.conf["collapseTime"] - return self.col.backend.learning_congrats_msg(abs(next), remaining) + return self.col.backend.congrats_learn_message(abs(next), remaining) else: return "" diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 4d210734e..2fb769b26 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -356,6 +356,28 @@ impl BackendService for Backend { }) } + fn find_and_replace(&mut self, input: pb::FindAndReplaceIn) -> BackendResult { + let mut search = if input.regex { + input.search + } else { + regex::escape(&input.search) + }; + if !input.match_case { + search = format!("(?i){}", search); + } + let nids = input.nids.into_iter().map(NoteID).collect(); + let field_name = if input.field_name.is_empty() { + None + } else { + Some(input.field_name) + }; + let repl = input.replacement; + self.with_col(|col| { + col.find_and_replace(nids, &search, &repl, field_name) + .map(|cnt| pb::UInt32 { val: cnt as u32 }) + }) + } + // scheduling //----------------------------------------------- @@ -369,6 +391,25 @@ impl BackendService for Backend { }) } + fn set_local_minutes_west(&mut self, input: pb::Int32) -> BackendResult { + self.with_col(|col| { + col.transact(None, |col| { + col.set_local_mins_west(input.val).map(Into::into) + }) + }) + } + + fn studied_today(&mut self, input: pb::StudiedTodayIn) -> BackendResult { + Ok(studied_today(input.cards as usize, input.seconds as f32, &self.i18n).into()) + } + + fn congrats_learn_message( + &mut self, + input: pb::CongratsLearnMessageIn, + ) -> BackendResult { + Ok(learning_congrats(input.remaining as usize, input.next_due, &self.i18n).into()) + } + // decks //----------------------------------------------- @@ -740,23 +781,46 @@ impl BackendService for Backend { // media //------------------------------------------------------------------- - fn sync_media(&mut self, input: SyncMediaIn) -> BackendResult { - let mut guard = self.col.lock().unwrap(); + fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> BackendResult { + self.with_col(|col| { + let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; + let mut ctx = mgr.dbctx(); + Ok(mgr + .add_file(&mut ctx, &input.desired_name, &input.data)? + .to_string() + .into()) + }) + } - let col = guard.as_mut().unwrap(); - col.set_media_sync_running()?; + fn empty_trash(&mut self, _input: Empty) -> BackendResult { + let callback = + |progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32)); - let folder = col.media_folder.clone(); - let db = col.media_db.clone(); - let log = col.log.clone(); + self.with_col(|col| { + let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; + col.transact(None, |ctx| { + let mut checker = MediaChecker::new(ctx, &mgr, callback); - drop(guard); + checker.empty_trash() + }) + }) + .map(Into::into) + } - let res = self.sync_media_inner(input, folder, db, log); + fn restore_trash(&mut self, _input: Empty) -> BackendResult { + let callback = + |progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32)); - self.with_col(|col| col.set_media_sync_finished())?; + self.with_col(|col| { + let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; - res.map(Into::into) + col.transact(None, |ctx| { + let mut checker = MediaChecker::new(ctx, &mgr, callback); + + checker.restore_trash() + }) + }) + .map(Into::into) } fn trash_media_files(&mut self, input: pb::TrashMediaFilesIn) -> BackendResult { @@ -790,7 +854,8 @@ impl BackendService for Backend { }) } - // misc + // collection + //------------------------------------------------------------------- fn check_database(&mut self, _input: pb::Empty) -> BackendResult { self.with_col(|col| { @@ -799,6 +864,89 @@ impl BackendService for Backend { }) }) } + + fn open_collection(&mut self, input: pb::OpenCollectionIn) -> BackendResult { + let mut col = self.col.lock().unwrap(); + if col.is_some() { + return Err(AnkiError::CollectionAlreadyOpen); + } + + let mut path = input.collection_path.clone(); + path.push_str(".log"); + + let log_path = match input.log_path.as_str() { + "" => None, + path => Some(path), + }; + let logger = default_logger(log_path)?; + + let new_col = open_collection( + input.collection_path, + input.media_folder_path, + input.media_db_path, + self.server, + self.i18n.clone(), + logger, + )?; + + *col = Some(new_col); + + Ok(().into()) + } + + fn close_collection(&mut self, input: pb::CloseCollectionIn) -> BackendResult { + let mut col = self.col.lock().unwrap(); + if col.is_none() { + return Err(AnkiError::CollectionNotOpen); + } + + if !col.as_ref().unwrap().can_close() { + return Err(AnkiError::invalid_input("can't close yet")); + } + + let col_inner = col.take().unwrap(); + if input.downgrade_to_schema11 { + let log = log::terminal(); + if let Err(e) = col_inner.close(input.downgrade_to_schema11) { + error!(log, " failed: {:?}", e); + } + } + + Ok(().into()) + } + + // sync + //------------------------------------------------------------------- + + fn sync_media(&mut self, input: SyncMediaIn) -> BackendResult { + let mut guard = self.col.lock().unwrap(); + + let col = guard.as_mut().unwrap(); + col.set_media_sync_running()?; + + let folder = col.media_folder.clone(); + let db = col.media_db.clone(); + let log = col.log.clone(); + + drop(guard); + + let res = self.sync_media_inner(input, folder, db, log); + + self.with_col(|col| col.set_media_sync_finished())?; + + res.map(Into::into) + } + + fn abort_media_sync(&mut self, _input: Empty) -> BackendResult { + if let Some(handle) = self.media_sync_abort.take() { + handle.abort(); + } + Ok(().into()) + } + + fn before_upload(&mut self, _input: Empty) -> BackendResult { + self.with_col(|col| col.before_upload().map(Into::into)) + } } impl Backend { @@ -891,43 +1039,8 @@ impl Backend { ) -> Result { use pb::backend_output::Value as OValue; Ok(match ival { - Value::AddMediaFile(input) => OValue::AddMediaFile(self.add_media_file(input)?), Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)), Value::FormatTimeSpan(input) => OValue::FormatTimeSpan(self.format_time_span(input)), - Value::StudiedToday(input) => OValue::StudiedToday(studied_today( - input.cards as usize, - input.seconds as f32, - &self.i18n, - )), - Value::CongratsLearnMsg(input) => OValue::CongratsLearnMsg(learning_congrats( - input.remaining as usize, - input.next_due, - &self.i18n, - )), - Value::EmptyTrash(_) => { - self.empty_trash()?; - OValue::EmptyTrash(Empty {}) - } - Value::RestoreTrash(_) => { - self.restore_trash()?; - OValue::RestoreTrash(Empty {}) - } - Value::OpenCollection(input) => { - self.open_collection(input)?; - OValue::OpenCollection(Empty {}) - } - Value::CloseCollection(input) => { - self.close_collection(input.downgrade_to_schema11)?; - OValue::CloseCollection(Empty {}) - } - Value::AbortMediaSync(_) => { - self.abort_media_sync(); - OValue::AbortMediaSync(pb::Empty {}) - } - Value::BeforeUpload(_) => { - self.before_upload()?; - OValue::BeforeUpload(pb::Empty {}) - } Value::AllTags(_) => OValue::AllTags(self.all_tags()?), Value::RegisterTags(input) => OValue::RegisterTags(self.register_tags(input)?), Value::GetChangedTags(usn) => OValue::GetChangedTags(self.get_changed_tags(usn)?), @@ -942,11 +1055,6 @@ impl Backend { pb::Empty {} }), Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?), - Value::FindAndReplace(input) => OValue::FindAndReplace(self.find_and_replace(input)?), - Value::SetLocalMinutesWest(mins) => OValue::SetLocalMinutesWest({ - self.set_local_mins_west(mins)?; - pb::Empty {} - }), Value::GetPreferences(_) => OValue::GetPreferences(self.get_preferences()?), Value::SetPreferences(prefs) => OValue::SetPreferences({ self.set_preferences(prefs)?; @@ -955,56 +1063,6 @@ impl Backend { }) } - fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<()> { - let mut col = self.col.lock().unwrap(); - if col.is_some() { - return Err(AnkiError::CollectionAlreadyOpen); - } - - let mut path = input.collection_path.clone(); - path.push_str(".log"); - - let log_path = match input.log_path.as_str() { - "" => None, - path => Some(path), - }; - let logger = default_logger(log_path)?; - - let new_col = open_collection( - input.collection_path, - input.media_folder_path, - input.media_db_path, - self.server, - self.i18n.clone(), - logger, - )?; - - *col = Some(new_col); - - Ok(()) - } - - fn close_collection(&self, downgrade: bool) -> Result<()> { - let mut col = self.col.lock().unwrap(); - if col.is_none() { - return Err(AnkiError::CollectionNotOpen); - } - - if !col.as_ref().unwrap().can_close() { - return Err(AnkiError::invalid_input("can't close yet")); - } - - let col_inner = col.take().unwrap(); - if downgrade { - let log = log::terminal(); - if let Err(e) = col_inner.close(downgrade) { - error!(log, " failed: {:?}", e); - } - } - - Ok(()) - } - fn fire_progress_callback(&self, progress: Progress) -> bool { if let Some(cb) = &self.progress_callback { let bytes = progress_to_proto_bytes(progress, &self.i18n); @@ -1018,16 +1076,6 @@ impl Backend { self.progress_callback = progress_cb; } - fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> Result { - self.with_col(|col| { - let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; - let mut ctx = mgr.dbctx(); - Ok(mgr - .add_file(&mut ctx, &input.desired_name, &input.data)? - .into()) - }) - } - fn sync_media_inner( &mut self, input: pb::SyncMediaIn, @@ -1057,12 +1105,6 @@ impl Backend { ret } - fn abort_media_sync(&mut self) { - if let Some(handle) = self.media_sync_abort.take() { - handle.abort(); - } - } - fn translate_string(&self, input: pb::TranslateStringIn) -> String { let key = match pb::FluentString::from_i32(input.key) { Some(key) => key, @@ -1094,43 +1136,10 @@ impl Backend { } } - fn empty_trash(&self) -> Result<()> { - let callback = - |progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32)); - - self.with_col(|col| { - let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; - col.transact(None, |ctx| { - let mut checker = MediaChecker::new(ctx, &mgr, callback); - - checker.empty_trash() - }) - }) - } - - fn restore_trash(&self) -> Result<()> { - let callback = - |progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32)); - - self.with_col(|col| { - let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; - - col.transact(None, |ctx| { - let mut checker = MediaChecker::new(ctx, &mgr, callback); - - checker.restore_trash() - }) - }) - } - pub fn db_command(&self, input: &[u8]) -> Result { self.with_col(|col| db_command_bytes(&col.storage, input)) } - fn before_upload(&self) -> Result<()> { - self.with_col(|col| col.before_upload()) - } - fn all_tags(&self) -> Result { let tags = self.with_col(|col| col.storage.all_tags())?; let tags: Vec<_> = tags @@ -1209,32 +1218,6 @@ impl Backend { }) } - fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> Result { - let mut search = if input.regex { - input.search - } else { - regex::escape(&input.search) - }; - if !input.match_case { - search = format!("(?i){}", search); - } - let nids = input.nids.into_iter().map(NoteID).collect(); - let field_name = if input.field_name.is_empty() { - None - } else { - Some(input.field_name) - }; - let repl = input.replacement; - self.with_col(|col| { - col.find_and_replace(nids, &search, &repl, field_name) - .map(|cnt| cnt 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))) - } - fn get_preferences(&self) -> Result { self.with_col(|col| col.get_preferences()) } diff --git a/rspy/src/lib.rs b/rspy/src/lib.rs index 4fc637757..beac28fc2 100644 --- a/rspy/src/lib.rs +++ b/rspy/src/lib.rs @@ -82,6 +82,17 @@ fn want_release_gil(method: u32) -> bool { BackendMethod::GetNotetypeIDByName => true, BackendMethod::RemoveNotetype => true, BackendMethod::CheckDatabase => true, + BackendMethod::FindAndReplace => true, + BackendMethod::SetLocalMinutesWest => false, + BackendMethod::StudiedToday => false, + BackendMethod::CongratsLearnMessage => false, + BackendMethod::AddMediaFile => true, + BackendMethod::EmptyTrash => true, + BackendMethod::RestoreTrash => true, + BackendMethod::OpenCollection => true, + BackendMethod::CloseCollection => true, + BackendMethod::AbortMediaSync => true, + BackendMethod::BeforeUpload => true, } } else { false