mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
migrate more scheduling/media/etc
almost there
This commit is contained in:
parent
a105037ec9
commit
4bf8175bcb
8 changed files with 328 additions and 341 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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@@
|
||||
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -356,6 +356,28 @@ impl BackendService for Backend {
|
|||
})
|
||||
}
|
||||
|
||||
fn find_and_replace(&mut self, input: pb::FindAndReplaceIn) -> BackendResult<pb::UInt32> {
|
||||
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<Empty> {
|
||||
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<pb::String> {
|
||||
Ok(studied_today(input.cards as usize, input.seconds as f32, &self.i18n).into())
|
||||
}
|
||||
|
||||
fn congrats_learn_message(
|
||||
&mut self,
|
||||
input: pb::CongratsLearnMessageIn,
|
||||
) -> BackendResult<pb::String> {
|
||||
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<Empty> {
|
||||
let mut guard = self.col.lock().unwrap();
|
||||
fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> BackendResult<pb::String> {
|
||||
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<Empty> {
|
||||
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<Empty> {
|
||||
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<Empty> {
|
||||
|
@ -790,7 +854,8 @@ impl BackendService for Backend {
|
|||
})
|
||||
}
|
||||
|
||||
// misc
|
||||
// collection
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn check_database(&mut self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
|
||||
self.with_col(|col| {
|
||||
|
@ -799,6 +864,89 @@ impl BackendService for Backend {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn open_collection(&mut self, input: pb::OpenCollectionIn) -> BackendResult<Empty> {
|
||||
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<Empty> {
|
||||
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<Empty> {
|
||||
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<Empty> {
|
||||
if let Some(handle) = self.media_sync_abort.take() {
|
||||
handle.abort();
|
||||
}
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
fn before_upload(&mut self, _input: Empty) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.before_upload().map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
|
@ -891,43 +1039,8 @@ impl Backend {
|
|||
) -> Result<pb::backend_output::Value> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<pb::AllTagsOut> {
|
||||
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<u32> {
|
||||
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<pb::Preferences> {
|
||||
self.with_col(|col| col.get_preferences())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue