mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
migrate the remaining methods
This commit is contained in:
parent
4bf8175bcb
commit
89dde3aeb0
13 changed files with 390 additions and 444 deletions
|
@ -158,6 +158,27 @@ service BackendService {
|
|||
rpc SyncMedia (SyncMediaIn) returns (Empty);
|
||||
rpc AbortMediaSync (Empty) returns (Empty);
|
||||
rpc BeforeUpload (Empty) returns (Empty);
|
||||
|
||||
// translation/messages
|
||||
|
||||
rpc TranslateString (TranslateStringIn) returns (String);
|
||||
rpc FormatTimespan (FormatTimespanIn) returns (String);
|
||||
|
||||
// tags
|
||||
|
||||
rpc RegisterTags (RegisterTagsIn) returns (Bool);
|
||||
rpc AllTags (Empty) returns (AllTagsOut);
|
||||
rpc GetChangedTags (Int32) returns (GetChangedTagsOut);
|
||||
|
||||
// config/preferences
|
||||
|
||||
rpc GetConfigJson (String) returns (Json);
|
||||
rpc SetConfigJson (SetConfigJsonIn) returns (Empty);
|
||||
rpc RemoveConfig (String) returns (Empty);
|
||||
rpc SetAllConfig (Json) returns (Empty);
|
||||
rpc GetAllConfig (Empty) returns (Json);
|
||||
rpc GetPreferences (Empty) returns (Preferences);
|
||||
rpc SetPreferences (Preferences) returns (Empty);
|
||||
}
|
||||
|
||||
// Protobuf stored in .anki2 files
|
||||
|
@ -398,49 +419,9 @@ message I18nBackendInit {
|
|||
string locale_folder_path = 5;
|
||||
}
|
||||
|
||||
// Legacy enum - needs migrating to RPC above
|
||||
// Errors
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
message BackendInput {
|
||||
oneof value {
|
||||
TranslateStringIn translate_string = 30;
|
||||
FormatTimeSpanIn format_time_span = 31;
|
||||
|
||||
RegisterTagsIn register_tags = 48;
|
||||
Empty all_tags = 50;
|
||||
int32 get_changed_tags = 51;
|
||||
|
||||
string get_config_json = 52;
|
||||
SetConfigJson set_config_json = 53;
|
||||
bytes set_all_config = 54;
|
||||
Empty get_all_config = 55;
|
||||
|
||||
Empty get_preferences = 84;
|
||||
Preferences set_preferences = 85;
|
||||
}
|
||||
}
|
||||
|
||||
message BackendOutput {
|
||||
oneof value {
|
||||
string translate_string = 30;
|
||||
string format_time_span = 31;
|
||||
|
||||
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;
|
||||
|
||||
Preferences get_preferences = 84;
|
||||
Empty set_preferences = 85;
|
||||
|
||||
BackendError error = 2047;
|
||||
}
|
||||
}
|
||||
|
||||
message BackendError {
|
||||
// localized error description suitable for displaying to the user
|
||||
string localized = 1;
|
||||
|
@ -504,6 +485,10 @@ message MediaSyncUploadProgress {
|
|||
uint32 deletions = 2;
|
||||
}
|
||||
|
||||
// Messages
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
message SchedTimingTodayOut {
|
||||
uint32 days_elapsed = 1;
|
||||
int64 next_day_at = 2;
|
||||
|
@ -632,7 +617,7 @@ message TranslateArgValue {
|
|||
}
|
||||
}
|
||||
|
||||
message FormatTimeSpanIn {
|
||||
message FormatTimespanIn {
|
||||
enum Context {
|
||||
PRECISE = 0;
|
||||
ANSWER_BUTTONS = 1;
|
||||
|
@ -736,12 +721,9 @@ message GetChangedTagsOut {
|
|||
repeated string tags = 1;
|
||||
}
|
||||
|
||||
message SetConfigJson {
|
||||
message SetConfigJsonIn {
|
||||
string key = 1;
|
||||
oneof op {
|
||||
bytes val = 2;
|
||||
Empty remove = 3;
|
||||
}
|
||||
bytes val = 2;
|
||||
}
|
||||
|
||||
enum StockNoteType {
|
||||
|
|
|
@ -26,7 +26,13 @@ from anki.lang import _
|
|||
from anki.media import MediaManager, media_paths_from_col_path
|
||||
from anki.models import ModelManager
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import TR, DBError, RustBackend, pb
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
DBError,
|
||||
FormatTimeSpanContext,
|
||||
RustBackend,
|
||||
pb,
|
||||
)
|
||||
from anki.sched import Scheduler as V1Scheduler
|
||||
from anki.schedv2 import Scheduler as V2Scheduler
|
||||
from anki.tags import TagManager
|
||||
|
@ -65,13 +71,23 @@ class Collection:
|
|||
n = os.path.splitext(os.path.basename(self.path))[0]
|
||||
return n
|
||||
|
||||
def tr(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
||||
return self.backend.translate(key, **kwargs)
|
||||
|
||||
def weakref(self) -> Collection:
|
||||
"Shortcut to create a weak reference that doesn't break code completion."
|
||||
return weakref.proxy(self)
|
||||
|
||||
# I18n/messages
|
||||
##########################################################################
|
||||
|
||||
def tr(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
||||
return self.backend.translate(key, **kwargs)
|
||||
|
||||
def format_timespan(
|
||||
self,
|
||||
seconds: float,
|
||||
context: FormatTimeSpanContext = FormatTimeSpanContext.INTERVALS,
|
||||
) -> str:
|
||||
return self.backend.format_timespan(seconds, context)
|
||||
|
||||
# Scheduler
|
||||
##########################################################################
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import weakref
|
|||
from typing import Any
|
||||
|
||||
import anki
|
||||
from anki.rsbackend import NotFoundError, from_json_bytes, to_json_bytes
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
|
@ -30,10 +31,13 @@ class ConfigManager:
|
|||
self.col = col.weakref()
|
||||
|
||||
def get_immutable(self, key: str) -> Any:
|
||||
return self.col.backend.get_config_json(key)
|
||||
try:
|
||||
return from_json_bytes(self.col.backend.get_config_json(key))
|
||||
except NotFoundError:
|
||||
raise KeyError
|
||||
|
||||
def set(self, key: str, val: Any) -> None:
|
||||
self.col.backend.set_config_json(key, val)
|
||||
self.col.backend.set_config_json(key, to_json_bytes(val))
|
||||
|
||||
def remove(self, key: str) -> None:
|
||||
self.col.backend.remove_config(key)
|
||||
|
|
|
@ -153,7 +153,7 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
|||
|
||||
MediaSyncProgress = pb.MediaSyncProgress
|
||||
|
||||
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
|
||||
FormatTimeSpanContext = pb.FormatTimespanIn.Context
|
||||
|
||||
|
||||
class ProgressKind(enum.Enum):
|
||||
|
@ -203,35 +203,6 @@ class RustBackend:
|
|||
self._backend = ankirspy.open_backend(init_msg.SerializeToString())
|
||||
self._backend.set_progress_callback(_on_progress)
|
||||
|
||||
def _run_command(
|
||||
self, input: pb.BackendInput, release_gil: bool = False
|
||||
) -> pb.BackendOutput:
|
||||
input_bytes = input.SerializeToString()
|
||||
output_bytes = self._backend.command(input_bytes, release_gil)
|
||||
output = pb.BackendOutput()
|
||||
output.ParseFromString(output_bytes)
|
||||
kind = output.WhichOneof("value")
|
||||
if kind == "error":
|
||||
raise proto_exception_to_native(output.error)
|
||||
else:
|
||||
return output
|
||||
|
||||
def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
||||
return self._run_command(
|
||||
pb.BackendInput(translate_string=translate_string_in(key, **kwargs))
|
||||
).translate_string
|
||||
|
||||
def format_time_span(
|
||||
self,
|
||||
seconds: float,
|
||||
context: FormatTimeSpanContext = FormatTimeSpanContext.INTERVALS,
|
||||
) -> str:
|
||||
return self._run_command(
|
||||
pb.BackendInput(
|
||||
format_time_span=pb.FormatTimeSpanIn(seconds=seconds, context=context)
|
||||
)
|
||||
).format_time_span
|
||||
|
||||
def db_query(
|
||||
self, sql: str, args: Sequence[ValueForDB], first_row_only: bool
|
||||
) -> List[DBRow]:
|
||||
|
@ -254,76 +225,23 @@ class RustBackend:
|
|||
def _db_command(self, input: Dict[str, Any]) -> Any:
|
||||
return orjson.loads(self._backend.db_command(orjson.dumps(input)))
|
||||
|
||||
def all_tags(self) -> Iterable[TagUsnTuple]:
|
||||
return self._run_command(pb.BackendInput(all_tags=pb.Empty())).all_tags.tags
|
||||
def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
||||
return self.translate_string(translate_string_in(key, **kwargs))
|
||||
|
||||
def register_tags(self, tags: str, usn: Optional[int], clear_first: bool) -> bool:
|
||||
if usn is None:
|
||||
preserve_usn = False
|
||||
usn_ = 0
|
||||
else:
|
||||
usn_ = usn
|
||||
preserve_usn = True
|
||||
|
||||
return self._run_command(
|
||||
pb.BackendInput(
|
||||
register_tags=pb.RegisterTagsIn(
|
||||
tags=tags,
|
||||
usn=usn_,
|
||||
preserve_usn=preserve_usn,
|
||||
clear_first=clear_first,
|
||||
)
|
||||
)
|
||||
).register_tags
|
||||
|
||||
def get_changed_tags(self, usn: int) -> List[str]:
|
||||
return list(
|
||||
self._run_command(
|
||||
pb.BackendInput(get_changed_tags=usn)
|
||||
).get_changed_tags.tags
|
||||
def format_time_span(
|
||||
self,
|
||||
seconds: float,
|
||||
context: FormatTimeSpanContext = FormatTimeSpanContext.INTERVALS,
|
||||
) -> str:
|
||||
print(
|
||||
"please use col.format_timespan() instead of col.backend.format_time_span()"
|
||||
)
|
||||
return self.format_timespan(seconds, context)
|
||||
|
||||
def get_config_json(self, key: str) -> Any:
|
||||
b = self._run_command(pb.BackendInput(get_config_json=key)).get_config_json
|
||||
if b == b"":
|
||||
raise KeyError
|
||||
return orjson.loads(b)
|
||||
|
||||
def set_config_json(self, key: str, val: Any):
|
||||
self._run_command(
|
||||
pb.BackendInput(
|
||||
set_config_json=pb.SetConfigJson(key=key, val=orjson.dumps(val))
|
||||
)
|
||||
)
|
||||
|
||||
def remove_config(self, key: str):
|
||||
self._run_command(
|
||||
pb.BackendInput(
|
||||
set_config_json=pb.SetConfigJson(key=key, remove=pb.Empty())
|
||||
)
|
||||
)
|
||||
|
||||
def get_all_config(self) -> Dict[str, Any]:
|
||||
jstr = self._run_command(
|
||||
pb.BackendInput(get_all_config=pb.Empty())
|
||||
).get_all_config
|
||||
return orjson.loads(jstr)
|
||||
|
||||
def set_all_config(self, conf: Dict[str, Any]):
|
||||
self._run_command(pb.BackendInput(set_all_config=orjson.dumps(conf)))
|
||||
|
||||
def get_preferences(self) -> pb.Preferences:
|
||||
return self._run_command(
|
||||
pb.BackendInput(get_preferences=pb.Empty())
|
||||
).get_preferences
|
||||
|
||||
def set_preferences(self, prefs: pb.Preferences) -> None:
|
||||
self._run_command(pb.BackendInput(set_preferences=prefs))
|
||||
|
||||
def _run_command2(self, method: int, input: Any) -> bytes:
|
||||
def _run_command(self, method: int, input: Any) -> bytes:
|
||||
input_bytes = input.SerializeToString()
|
||||
try:
|
||||
return self._backend.command2(method, input_bytes)
|
||||
return self._backend.command(method, input_bytes)
|
||||
except Exception as e:
|
||||
err_bytes = bytes(e.args[0])
|
||||
err = pb.BackendError()
|
||||
|
@ -338,7 +256,7 @@ class RustBackend:
|
|||
def extract_av_tags(self, text: str, question_side: bool) -> pb.ExtractAVTagsOut:
|
||||
input = pb.ExtractAVTagsIn(text=text, question_side=question_side)
|
||||
output = pb.ExtractAVTagsOut()
|
||||
output.ParseFromString(self._run_command2(1, input))
|
||||
output.ParseFromString(self._run_command(1, input))
|
||||
return output
|
||||
|
||||
def extract_latex(
|
||||
|
@ -346,19 +264,19 @@ class RustBackend:
|
|||
) -> pb.ExtractLatexOut:
|
||||
input = pb.ExtractLatexIn(text=text, svg=svg, expand_clozes=expand_clozes)
|
||||
output = pb.ExtractLatexOut()
|
||||
output.ParseFromString(self._run_command2(2, input))
|
||||
output.ParseFromString(self._run_command(2, input))
|
||||
return output
|
||||
|
||||
def get_empty_cards(self) -> pb.EmptyCardsReport:
|
||||
input = pb.Empty()
|
||||
output = pb.EmptyCardsReport()
|
||||
output.ParseFromString(self._run_command2(3, input))
|
||||
output.ParseFromString(self._run_command(3, input))
|
||||
return output
|
||||
|
||||
def render_existing_card(self, card_id: int, browser: bool) -> pb.RenderCardOut:
|
||||
input = pb.RenderExistingCardIn(card_id=card_id, browser=browser)
|
||||
output = pb.RenderCardOut()
|
||||
output.ParseFromString(self._run_command2(4, input))
|
||||
output.ParseFromString(self._run_command(4, input))
|
||||
return output
|
||||
|
||||
def render_uncommitted_card(
|
||||
|
@ -368,25 +286,25 @@ class RustBackend:
|
|||
note=note, card_ord=card_ord, template=template, fill_empty=fill_empty
|
||||
)
|
||||
output = pb.RenderCardOut()
|
||||
output.ParseFromString(self._run_command2(5, input))
|
||||
output.ParseFromString(self._run_command(5, input))
|
||||
return output
|
||||
|
||||
def strip_av_tags(self, val: str) -> str:
|
||||
input = pb.String(val=val)
|
||||
output = pb.String()
|
||||
output.ParseFromString(self._run_command2(6, input))
|
||||
output.ParseFromString(self._run_command(6, input))
|
||||
return output.val
|
||||
|
||||
def search_cards(self, search: str, order: pb.SortOrder) -> Sequence[int]:
|
||||
input = pb.SearchCardsIn(search=search, order=order)
|
||||
output = pb.SearchCardsOut()
|
||||
output.ParseFromString(self._run_command2(7, input))
|
||||
output.ParseFromString(self._run_command(7, input))
|
||||
return output.card_ids
|
||||
|
||||
def search_notes(self, search: str) -> Sequence[int]:
|
||||
input = pb.SearchNotesIn(search=search)
|
||||
output = pb.SearchNotesOut()
|
||||
output.ParseFromString(self._run_command2(8, input))
|
||||
output.ParseFromString(self._run_command(8, input))
|
||||
return output.note_ids
|
||||
|
||||
def find_and_replace(
|
||||
|
@ -407,67 +325,67 @@ class RustBackend:
|
|||
field_name=field_name,
|
||||
)
|
||||
output = pb.UInt32()
|
||||
output.ParseFromString(self._run_command2(9, input))
|
||||
output.ParseFromString(self._run_command(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(10, input))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(11, input))
|
||||
return output
|
||||
|
||||
def sched_timing_today(self) -> pb.SchedTimingTodayOut:
|
||||
input = pb.Empty()
|
||||
output = pb.SchedTimingTodayOut()
|
||||
output.ParseFromString(self._run_command2(12, input))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(14, input))
|
||||
return output.val
|
||||
|
||||
def check_media(self) -> pb.CheckMediaOut:
|
||||
input = pb.Empty()
|
||||
output = pb.CheckMediaOut()
|
||||
output.ParseFromString(self._run_command2(15, input))
|
||||
output.ParseFromString(self._run_command(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(16, input))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(17, input))
|
||||
return output.val
|
||||
|
||||
def empty_trash(self) -> pb.Empty:
|
||||
input = pb.Empty()
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(18, input))
|
||||
output.ParseFromString(self._run_command(18, input))
|
||||
return output
|
||||
|
||||
def restore_trash(self) -> pb.Empty:
|
||||
input = pb.Empty()
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(19, input))
|
||||
output.ParseFromString(self._run_command(19, input))
|
||||
return output
|
||||
|
||||
def add_or_update_deck_legacy(
|
||||
|
@ -477,37 +395,37 @@ class RustBackend:
|
|||
deck=deck, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||
)
|
||||
output = pb.DeckID()
|
||||
output.ParseFromString(self._run_command2(20, input))
|
||||
output.ParseFromString(self._run_command(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(21, input))
|
||||
output.ParseFromString(self._run_command(21, input))
|
||||
return output
|
||||
|
||||
def deck_tree_legacy(self) -> bytes:
|
||||
input = pb.Empty()
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command2(22, input))
|
||||
output.ParseFromString(self._run_command(22, input))
|
||||
return output.json
|
||||
|
||||
def get_all_decks_legacy(self) -> bytes:
|
||||
input = pb.Empty()
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command2(23, input))
|
||||
output.ParseFromString(self._run_command(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(24, input))
|
||||
output.ParseFromString(self._run_command(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(25, input))
|
||||
output.ParseFromString(self._run_command(25, input))
|
||||
return output.json
|
||||
|
||||
def get_deck_names(
|
||||
|
@ -517,19 +435,19 @@ class RustBackend:
|
|||
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
||||
)
|
||||
output = pb.DeckNames()
|
||||
output.ParseFromString(self._run_command2(26, input))
|
||||
output.ParseFromString(self._run_command(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(27, input))
|
||||
output.ParseFromString(self._run_command(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(28, input))
|
||||
output.ParseFromString(self._run_command(28, input))
|
||||
return output
|
||||
|
||||
def add_or_update_deck_config_legacy(
|
||||
|
@ -539,76 +457,76 @@ class RustBackend:
|
|||
config=config, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||
)
|
||||
output = pb.DeckConfigID()
|
||||
output.ParseFromString(self._run_command2(29, input))
|
||||
output.ParseFromString(self._run_command(29, input))
|
||||
return output.dcid
|
||||
|
||||
def all_deck_config_legacy(self) -> bytes:
|
||||
input = pb.Empty()
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command2(30, input))
|
||||
output.ParseFromString(self._run_command(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(31, input))
|
||||
output.ParseFromString(self._run_command(31, input))
|
||||
return output.json
|
||||
|
||||
def new_deck_config_legacy(self) -> bytes:
|
||||
input = pb.Empty()
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command2(32, input))
|
||||
output.ParseFromString(self._run_command(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(33, input))
|
||||
output.ParseFromString(self._run_command(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(34, input))
|
||||
output.ParseFromString(self._run_command(34, input))
|
||||
return output
|
||||
|
||||
def update_card(self, input: pb.Card) -> pb.Empty:
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(35, input))
|
||||
output.ParseFromString(self._run_command(35, input))
|
||||
return output
|
||||
|
||||
def add_card(self, input: pb.Card) -> int:
|
||||
output = pb.CardID()
|
||||
output.ParseFromString(self._run_command2(36, input))
|
||||
output.ParseFromString(self._run_command(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(37, input))
|
||||
output.ParseFromString(self._run_command(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(38, input))
|
||||
output.ParseFromString(self._run_command(38, input))
|
||||
return output.nid
|
||||
|
||||
def update_note(self, input: pb.Note) -> pb.Empty:
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(39, input))
|
||||
output.ParseFromString(self._run_command(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(40, input))
|
||||
output.ParseFromString(self._run_command(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(41, input))
|
||||
output.ParseFromString(self._run_command(41, input))
|
||||
return output.val
|
||||
|
||||
def update_note_tags(
|
||||
|
@ -618,12 +536,12 @@ class RustBackend:
|
|||
nids=nids, tags=tags, replacement=replacement, regex=regex
|
||||
)
|
||||
output = pb.UInt32()
|
||||
output.ParseFromString(self._run_command2(42, input))
|
||||
output.ParseFromString(self._run_command(42, input))
|
||||
return output.val
|
||||
|
||||
def cloze_numbers_in_note(self, input: pb.Note) -> Sequence[int]:
|
||||
output = pb.ClozeNumbersInNoteOut()
|
||||
output.ParseFromString(self._run_command2(43, input))
|
||||
output.ParseFromString(self._run_command(43, input))
|
||||
return output.numbers
|
||||
|
||||
def after_note_updates(
|
||||
|
@ -635,13 +553,13 @@ class RustBackend:
|
|||
generate_cards=generate_cards,
|
||||
)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(44, input))
|
||||
output.ParseFromString(self._run_command(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(45, input))
|
||||
output.ParseFromString(self._run_command(45, input))
|
||||
return output.fields
|
||||
|
||||
def add_or_update_notetype(self, json: bytes, preserve_usn_and_mtime: bool) -> int:
|
||||
|
@ -649,43 +567,43 @@ class RustBackend:
|
|||
json=json, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||
)
|
||||
output = pb.NoteTypeID()
|
||||
output.ParseFromString(self._run_command2(46, input))
|
||||
output.ParseFromString(self._run_command(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(47, input))
|
||||
output.ParseFromString(self._run_command(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(48, input))
|
||||
output.ParseFromString(self._run_command(48, input))
|
||||
return output.json
|
||||
|
||||
def get_notetype_names(self) -> Sequence[pb.NoteTypeNameID]:
|
||||
input = pb.Empty()
|
||||
output = pb.NoteTypeNames()
|
||||
output.ParseFromString(self._run_command2(49, input))
|
||||
output.ParseFromString(self._run_command(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(50, input))
|
||||
output.ParseFromString(self._run_command(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(51, input))
|
||||
output.ParseFromString(self._run_command(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(52, input))
|
||||
output.ParseFromString(self._run_command(52, input))
|
||||
return output
|
||||
|
||||
def open_collection(
|
||||
|
@ -702,37 +620,114 @@ class RustBackend:
|
|||
log_path=log_path,
|
||||
)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(53, input))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(54, input))
|
||||
return output
|
||||
|
||||
def check_database(self) -> Sequence[str]:
|
||||
input = pb.Empty()
|
||||
output = pb.CheckDatabaseOut()
|
||||
output.ParseFromString(self._run_command2(55, input))
|
||||
output.ParseFromString(self._run_command(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))
|
||||
output.ParseFromString(self._run_command(56, input))
|
||||
return output
|
||||
|
||||
def abort_media_sync(self) -> pb.Empty:
|
||||
input = pb.Empty()
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(57, input))
|
||||
output.ParseFromString(self._run_command(57, input))
|
||||
return output
|
||||
|
||||
def before_upload(self) -> pb.Empty:
|
||||
input = pb.Empty()
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command2(58, input))
|
||||
output.ParseFromString(self._run_command(58, input))
|
||||
return output
|
||||
|
||||
def translate_string(self, input: pb.TranslateStringIn) -> str:
|
||||
output = pb.String()
|
||||
output.ParseFromString(self._run_command(59, input))
|
||||
return output.val
|
||||
|
||||
def format_timespan(
|
||||
self, seconds: float, context: pb.FormatTimespanIn.Context
|
||||
) -> str:
|
||||
input = pb.FormatTimespanIn(seconds=seconds, context=context)
|
||||
output = pb.String()
|
||||
output.ParseFromString(self._run_command(60, input))
|
||||
return output.val
|
||||
|
||||
def register_tags(
|
||||
self, tags: str, preserve_usn: bool, usn: int, clear_first: bool
|
||||
) -> bool:
|
||||
input = pb.RegisterTagsIn(
|
||||
tags=tags, preserve_usn=preserve_usn, usn=usn, clear_first=clear_first
|
||||
)
|
||||
output = pb.Bool()
|
||||
output.ParseFromString(self._run_command(61, input))
|
||||
return output.val
|
||||
|
||||
def all_tags(self) -> Sequence[pb.TagUsnTuple]:
|
||||
input = pb.Empty()
|
||||
output = pb.AllTagsOut()
|
||||
output.ParseFromString(self._run_command(62, input))
|
||||
return output.tags
|
||||
|
||||
def get_changed_tags(self, val: int) -> Sequence[str]:
|
||||
input = pb.Int32(val=val)
|
||||
output = pb.GetChangedTagsOut()
|
||||
output.ParseFromString(self._run_command(63, input))
|
||||
return output.tags
|
||||
|
||||
def get_config_json(self, val: str) -> bytes:
|
||||
input = pb.String(val=val)
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command(64, input))
|
||||
return output.json
|
||||
|
||||
def set_config_json(self, key: str, val: bytes) -> pb.Empty:
|
||||
input = pb.SetConfigJsonIn(key=key, val=val)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command(65, input))
|
||||
return output
|
||||
|
||||
def remove_config(self, val: str) -> pb.Empty:
|
||||
input = pb.String(val=val)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command(66, input))
|
||||
return output
|
||||
|
||||
def set_all_config(self, json: bytes) -> pb.Empty:
|
||||
input = pb.Json(json=json)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command(67, input))
|
||||
return output
|
||||
|
||||
def get_all_config(self) -> bytes:
|
||||
input = pb.Empty()
|
||||
output = pb.Json()
|
||||
output.ParseFromString(self._run_command(68, input))
|
||||
return output.json
|
||||
|
||||
def get_preferences(self) -> pb.CollectionSchedulingSettings:
|
||||
input = pb.Empty()
|
||||
output = pb.Preferences()
|
||||
output.ParseFromString(self._run_command(69, input))
|
||||
return output.sched
|
||||
|
||||
def set_preferences(self, sched: pb.CollectionSchedulingSettings) -> pb.Empty:
|
||||
input = pb.Preferences(sched=sched)
|
||||
output = pb.Empty()
|
||||
output.ParseFromString(self._run_command(70, input))
|
||||
return output
|
||||
|
||||
# @@AUTOGEN@@
|
||||
|
|
|
@ -1369,9 +1369,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
|||
ivl_secs = self.nextIvl(card, ease)
|
||||
if not ivl_secs:
|
||||
return _("(end)")
|
||||
s = self.col.backend.format_time_span(
|
||||
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
|
||||
)
|
||||
s = self.col.format_timespan(ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS)
|
||||
if ivl_secs < self.col.conf["collapseTime"]:
|
||||
s = "<" + s
|
||||
return s
|
||||
|
|
|
@ -52,9 +52,7 @@ class CardStats:
|
|||
if next:
|
||||
self.addLine(self.col.tr(TR.STATISTICS_DUE_DATE), next)
|
||||
if c.queue == QUEUE_TYPE_REV:
|
||||
self.addLine(
|
||||
_("Interval"), self.col.backend.format_time_span(c.ivl * 86400)
|
||||
)
|
||||
self.addLine(_("Interval"), self.col.format_timespan(c.ivl * 86400))
|
||||
self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0))
|
||||
self.addLine(_("Reviews"), "%d" % c.reps)
|
||||
self.addLine(_("Lapses"), "%d" % c.lapses)
|
||||
|
@ -86,9 +84,7 @@ class CardStats:
|
|||
return time.strftime("%Y-%m-%d", time.localtime(tm))
|
||||
|
||||
def time(self, tm: float) -> str:
|
||||
return self.col.backend.format_time_span(
|
||||
tm, context=FormatTimeSpanContext.PRECISE
|
||||
)
|
||||
return self.col.format_timespan(tm, context=FormatTimeSpanContext.PRECISE)
|
||||
|
||||
|
||||
# Collection stats
|
||||
|
@ -645,12 +641,8 @@ group by day order by day)"""
|
|||
),
|
||||
)
|
||||
i: List[str] = []
|
||||
self._line(
|
||||
i, _("Average interval"), self.col.backend.format_time_span(avg * 86400)
|
||||
)
|
||||
self._line(
|
||||
i, _("Longest interval"), self.col.backend.format_time_span(max_ * 86400)
|
||||
)
|
||||
self._line(i, _("Average interval"), self.col.format_timespan(avg * 86400))
|
||||
self._line(i, _("Longest interval"), self.col.format_timespan(max_ * 86400))
|
||||
return txt + self._lineTbl(i)
|
||||
|
||||
def _ivls(self) -> Tuple[List[Any], int]:
|
||||
|
|
|
@ -19,6 +19,8 @@ from . import hooks
|
|||
from .httpclient import HttpClient
|
||||
|
||||
# add-on compat
|
||||
from .rsbackend import from_json_bytes, to_json_bytes
|
||||
|
||||
AnkiRequestsClient = HttpClient
|
||||
|
||||
|
||||
|
@ -402,7 +404,7 @@ from notes where %s"""
|
|||
##########################################################################
|
||||
|
||||
def getTags(self) -> List:
|
||||
return self.col.backend.get_changed_tags(self.maxUsn)
|
||||
return list(self.col.backend.get_changed_tags(self.maxUsn))
|
||||
|
||||
def mergeTags(self, tags) -> None:
|
||||
self.col.tags.register(tags, usn=self.maxUsn)
|
||||
|
@ -448,10 +450,10 @@ from notes where %s"""
|
|||
##########################################################################
|
||||
|
||||
def getConf(self) -> Dict[str, Any]:
|
||||
return self.col.backend.get_all_config()
|
||||
return from_json_bytes(self.col.backend.get_all_config())
|
||||
|
||||
def mergeConf(self, conf: Dict[str, Any]) -> None:
|
||||
self.col.backend.set_all_config(conf)
|
||||
self.col.backend.set_all_config(to_json_bytes(conf))
|
||||
|
||||
|
||||
# HTTP syncing tools
|
||||
|
|
|
@ -36,7 +36,16 @@ class TagManager:
|
|||
def register(
|
||||
self, tags: Collection[str], usn: Optional[int] = None, clear=False
|
||||
) -> None:
|
||||
self.col.backend.register_tags(" ".join(tags), usn, clear)
|
||||
if usn is None:
|
||||
preserve_usn = False
|
||||
usn_ = 0
|
||||
else:
|
||||
usn_ = usn
|
||||
preserve_usn = True
|
||||
|
||||
self.col.backend.register_tags(
|
||||
tags=" ".join(tags), preserve_usn=preserve_usn, usn=usn_, clear_first=clear
|
||||
)
|
||||
|
||||
def registerNotes(self, nids: Optional[List[int]] = None) -> None:
|
||||
"Add any missing tags from notes to the tags list."
|
||||
|
|
|
@ -29,6 +29,9 @@ LABEL_OPTIONAL = 1
|
|||
LABEL_REQUIRED = 2
|
||||
LABEL_REPEATED = 3
|
||||
|
||||
# messages we don't want to unroll in codegen
|
||||
SKIP_UNROLL_INPUT = {"TranslateString"}
|
||||
|
||||
|
||||
def python_type(field):
|
||||
type = python_type_inner(field)
|
||||
|
@ -50,13 +53,20 @@ def python_type_inner(field):
|
|||
elif type == TYPE_BYTES:
|
||||
return "bytes"
|
||||
elif type == TYPE_MESSAGE:
|
||||
return "pb." + field.message_type.name
|
||||
return fullname(field.message_type.full_name)
|
||||
elif type == TYPE_ENUM:
|
||||
return "pb." + field.enum_type.name
|
||||
return fullname(field.enum_type.full_name)
|
||||
else:
|
||||
raise Exception(f"unknown type: {type}")
|
||||
|
||||
|
||||
def fullname(fullname):
|
||||
if "FluentString" in fullname:
|
||||
return fullname.replace("backend_proto", "anki.fluent_pb2")
|
||||
else:
|
||||
return fullname.replace("backend_proto", "pb")
|
||||
|
||||
|
||||
# get_deck_i_d -> get_deck_id etc
|
||||
def fix_snakecase(name):
|
||||
for fix in "a_v", "i_d":
|
||||
|
@ -80,14 +90,18 @@ def get_input_assign(msg):
|
|||
|
||||
def render_method(method, idx):
|
||||
input_name = method.input_type.name
|
||||
if input_name.endswith("In") or len(method.input_type.fields) < 2:
|
||||
if (
|
||||
(input_name.endswith("In") or len(method.input_type.fields) < 2)
|
||||
and not method.input_type.oneofs
|
||||
and not method.name in SKIP_UNROLL_INPUT
|
||||
):
|
||||
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 "
|
||||
f"input = {fullname(method.input_type.full_name)}({input_assign})\n "
|
||||
)
|
||||
else:
|
||||
input_args = f"self, input: pb.{input_name}"
|
||||
input_args = f"self, input: {fullname(method.input_type.full_name)}"
|
||||
input_assign_outer = ""
|
||||
name = fix_snakecase(stringcase.snakecase(method.name))
|
||||
if len(method.output_type.fields) == 1:
|
||||
|
@ -101,7 +115,7 @@ def render_method(method, idx):
|
|||
return f"""\
|
||||
def {name}({input_args}) -> {return_type}:
|
||||
{input_assign_outer}output = pb.{method.output_type.name}()
|
||||
output.ParseFromString(self._run_command2({idx+1}, input))
|
||||
output.ParseFromString(self._run_command({idx+1}, input))
|
||||
return output{single_field}
|
||||
"""
|
||||
|
||||
|
|
|
@ -323,7 +323,7 @@ class DataModel(QAbstractTableModel):
|
|||
return _("(new)")
|
||||
elif c.type == CARD_TYPE_LRN:
|
||||
return _("(learning)")
|
||||
return self.col.backend.format_time_span(c.ivl * 86400)
|
||||
return self.col.format_timespan(c.ivl * 86400)
|
||||
elif type == "cardEase":
|
||||
if c.type == CARD_TYPE_NEW:
|
||||
return _("(new)")
|
||||
|
@ -1491,7 +1491,7 @@ border: 1px solid #000; padding: 3px; '>%s</div>"""
|
|||
|
||||
s += ("<td align=right>%s</td>" * 2) % (
|
||||
"%d%%" % (factor / 10) if factor else "",
|
||||
self.col.backend.format_time_span(taken),
|
||||
self.col.format_timespan(taken),
|
||||
) + "</tr>"
|
||||
s += "</table>"
|
||||
if cnt < self.card.reps:
|
||||
|
|
|
@ -40,7 +40,7 @@ def stripSounds(text) -> str:
|
|||
|
||||
def fmtTimeSpan(time, pad=0, point=0, short=False, inTime=False, unit=99):
|
||||
print("fmtTimeSpan() has become col.backend.format_time_span()")
|
||||
return aqt.mw.col.backend.format_time_span(time)
|
||||
return aqt.mw.col.format_timespan(time)
|
||||
|
||||
|
||||
def install_pylib_legacy() -> None:
|
||||
|
|
|
@ -41,7 +41,7 @@ use crate::{
|
|||
use fluent::FluentValue;
|
||||
use futures::future::{AbortHandle, Abortable};
|
||||
use log::error;
|
||||
use pb::{backend_input::Value, BackendService};
|
||||
use pb::BackendService;
|
||||
use prost::Message;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -99,13 +99,6 @@ fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert an Anki error to a protobuf output.
|
||||
impl std::convert::From<pb::BackendError> for pb::backend_output::Value {
|
||||
fn from(err: pb::BackendError) -> Self {
|
||||
pb::backend_output::Value::Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<NetworkErrorKind> for i32 {
|
||||
fn from(e: NetworkErrorKind) -> Self {
|
||||
use pb::network_error::NetworkErrorKind as V;
|
||||
|
@ -947,6 +940,133 @@ impl BackendService for Backend {
|
|||
fn before_upload(&mut self, _input: Empty) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.before_upload().map(Into::into))
|
||||
}
|
||||
|
||||
// i18n/messages
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn translate_string(&mut self, input: pb::TranslateStringIn) -> BackendResult<pb::String> {
|
||||
let key = match pb::FluentString::from_i32(input.key) {
|
||||
Some(key) => key,
|
||||
None => return Ok("invalid key".to_string().into()),
|
||||
};
|
||||
|
||||
let map = input
|
||||
.args
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v)))
|
||||
.collect();
|
||||
|
||||
Ok(self.i18n.trn(key, map).into())
|
||||
}
|
||||
|
||||
fn format_timespan(&mut self, input: pb::FormatTimespanIn) -> BackendResult<pb::String> {
|
||||
let context = match pb::format_timespan_in::Context::from_i32(input.context) {
|
||||
Some(context) => context,
|
||||
None => return Ok("".to_string().into()),
|
||||
};
|
||||
Ok(match context {
|
||||
pb::format_timespan_in::Context::Precise => time_span(input.seconds, &self.i18n, true),
|
||||
pb::format_timespan_in::Context::Intervals => {
|
||||
time_span(input.seconds, &self.i18n, false)
|
||||
}
|
||||
pb::format_timespan_in::Context::AnswerButtons => {
|
||||
answer_button_time(input.seconds, &self.i18n)
|
||||
}
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
// tags
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn all_tags(&mut self, _input: Empty) -> BackendResult<pb::AllTagsOut> {
|
||||
let tags = self.with_col(|col| col.storage.all_tags())?;
|
||||
let tags: Vec<_> = tags
|
||||
.into_iter()
|
||||
.map(|(tag, usn)| pb::TagUsnTuple { tag, usn: usn.0 })
|
||||
.collect();
|
||||
Ok(pb::AllTagsOut { tags })
|
||||
}
|
||||
|
||||
fn register_tags(&mut self, input: pb::RegisterTagsIn) -> BackendResult<pb::Bool> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
let usn = if input.preserve_usn {
|
||||
Usn(input.usn)
|
||||
} else {
|
||||
col.usn()?
|
||||
};
|
||||
col.register_tags(&input.tags, usn, input.clear_first)
|
||||
.map(|val| pb::Bool { val })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_changed_tags(&mut self, input: pb::Int32) -> BackendResult<pb::GetChangedTagsOut> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
Ok(pb::GetChangedTagsOut {
|
||||
tags: col.storage.get_changed_tags(Usn(input.val))?,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// config/preferences
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn get_config_json(&mut self, input: pb::String) -> BackendResult<pb::Json> {
|
||||
self.with_col(|col| {
|
||||
let val: Option<JsonValue> = col.get_config_optional(input.val.as_str());
|
||||
val.ok_or(AnkiError::NotFound)
|
||||
.and_then(|v| serde_json::to_vec(&v).map_err(Into::into))
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_config_json(&mut self, input: pb::SetConfigJsonIn) -> BackendResult<Empty> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
// ensure it's a well-formed object
|
||||
let val: JsonValue = serde_json::from_slice(&input.val)?;
|
||||
col.set_config(input.key.as_str(), &val)
|
||||
})
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn remove_config(&mut self, input: pb::String) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.transact(None, |col| col.remove_config(input.val.as_str())))
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn set_all_config(&mut self, input: pb::Json) -> BackendResult<Empty> {
|
||||
let val: HashMap<String, JsonValue> = serde_json::from_slice(&input.json)?;
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
col.storage
|
||||
.set_all_config(val, col.usn()?, TimestampSecs::now())
|
||||
})
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn get_all_config(&mut self, _input: Empty) -> BackendResult<pb::Json> {
|
||||
self.with_col(|col| {
|
||||
let conf = col.storage.get_all_config()?;
|
||||
serde_json::to_vec(&conf).map_err(Into::into)
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn get_preferences(&mut self, _input: Empty) -> BackendResult<pb::Preferences> {
|
||||
self.with_col(|col| col.get_preferences())
|
||||
}
|
||||
|
||||
fn set_preferences(&mut self, input: pb::Preferences) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.transact(None, |col| col.set_preferences(input)))
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
|
@ -964,30 +1084,7 @@ impl Backend {
|
|||
&self.i18n
|
||||
}
|
||||
|
||||
/// Decode a request, process it, and return the encoded result.
|
||||
pub fn run_command_bytes(&mut self, req: &[u8]) -> Vec<u8> {
|
||||
let mut buf = vec![];
|
||||
|
||||
let req = match pb::BackendInput::decode(req) {
|
||||
Ok(req) => req,
|
||||
Err(_e) => {
|
||||
// unable to decode
|
||||
let err = AnkiError::invalid_input("couldn't decode backend request");
|
||||
let oerr = anki_error_to_proto_error(err, &self.i18n);
|
||||
let output = pb::BackendOutput {
|
||||
value: Some(oerr.into()),
|
||||
};
|
||||
output.encode(&mut buf).expect("encode failed");
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
let resp = self.run_command(req);
|
||||
resp.encode(&mut buf).expect("encode failed");
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn run_command_bytes2(
|
||||
pub fn run_command_bytes(
|
||||
&mut self,
|
||||
method: u32,
|
||||
input: &[u8],
|
||||
|
@ -1016,53 +1113,6 @@ impl Backend {
|
|||
)
|
||||
}
|
||||
|
||||
fn run_command(&mut self, input: pb::BackendInput) -> pb::BackendOutput {
|
||||
let oval = if let Some(ival) = input.value {
|
||||
match self.run_command_inner(ival) {
|
||||
Ok(output) => output,
|
||||
Err(err) => anki_error_to_proto_error(err, &self.i18n).into(),
|
||||
}
|
||||
} else {
|
||||
anki_error_to_proto_error(
|
||||
AnkiError::invalid_input("unrecognized backend input value"),
|
||||
&self.i18n,
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
pb::BackendOutput { value: Some(oval) }
|
||||
}
|
||||
|
||||
fn run_command_inner(
|
||||
&mut self,
|
||||
ival: pb::backend_input::Value,
|
||||
) -> Result<pb::backend_output::Value> {
|
||||
use pb::backend_output::Value as OValue;
|
||||
Ok(match ival {
|
||||
Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)),
|
||||
Value::FormatTimeSpan(input) => OValue::FormatTimeSpan(self.format_time_span(input)),
|
||||
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)?),
|
||||
Value::GetConfigJson(key) => OValue::GetConfigJson(self.get_config_json(&key)?),
|
||||
Value::SetConfigJson(input) => OValue::SetConfigJson({
|
||||
self.set_config_json(input)?;
|
||||
pb::Empty {}
|
||||
}),
|
||||
|
||||
Value::SetAllConfig(input) => OValue::SetConfigJson({
|
||||
self.set_all_config(&input)?;
|
||||
pb::Empty {}
|
||||
}),
|
||||
Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?),
|
||||
Value::GetPreferences(_) => OValue::GetPreferences(self.get_preferences()?),
|
||||
Value::SetPreferences(prefs) => OValue::SetPreferences({
|
||||
self.set_preferences(prefs)?;
|
||||
pb::Empty {}
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn fire_progress_callback(&self, progress: Progress) -> bool {
|
||||
if let Some(cb) = &self.progress_callback {
|
||||
let bytes = progress_to_proto_bytes(progress, &self.i18n);
|
||||
|
@ -1105,126 +1155,9 @@ impl Backend {
|
|||
ret
|
||||
}
|
||||
|
||||
fn translate_string(&self, input: pb::TranslateStringIn) -> String {
|
||||
let key = match pb::FluentString::from_i32(input.key) {
|
||||
Some(key) => key,
|
||||
None => return "invalid key".to_string(),
|
||||
};
|
||||
|
||||
let map = input
|
||||
.args
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v)))
|
||||
.collect();
|
||||
|
||||
self.i18n.trn(key, map)
|
||||
}
|
||||
|
||||
fn format_time_span(&self, input: pb::FormatTimeSpanIn) -> String {
|
||||
let context = match pb::format_time_span_in::Context::from_i32(input.context) {
|
||||
Some(context) => context,
|
||||
None => return "".to_string(),
|
||||
};
|
||||
match context {
|
||||
pb::format_time_span_in::Context::Precise => time_span(input.seconds, &self.i18n, true),
|
||||
pb::format_time_span_in::Context::Intervals => {
|
||||
time_span(input.seconds, &self.i18n, false)
|
||||
}
|
||||
pb::format_time_span_in::Context::AnswerButtons => {
|
||||
answer_button_time(input.seconds, &self.i18n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db_command(&self, input: &[u8]) -> Result<String> {
|
||||
self.with_col(|col| db_command_bytes(&col.storage, input))
|
||||
}
|
||||
|
||||
fn all_tags(&self) -> Result<pb::AllTagsOut> {
|
||||
let tags = self.with_col(|col| col.storage.all_tags())?;
|
||||
let tags: Vec<_> = tags
|
||||
.into_iter()
|
||||
.map(|(tag, usn)| pb::TagUsnTuple { tag, usn: usn.0 })
|
||||
.collect();
|
||||
Ok(pb::AllTagsOut { tags })
|
||||
}
|
||||
|
||||
fn register_tags(&self, input: pb::RegisterTagsIn) -> Result<bool> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
let usn = if input.preserve_usn {
|
||||
Usn(input.usn)
|
||||
} else {
|
||||
col.usn()?
|
||||
};
|
||||
col.register_tags(&input.tags, usn, input.clear_first)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_changed_tags(&self, usn: i32) -> Result<pb::GetChangedTagsOut> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
Ok(pb::GetChangedTagsOut {
|
||||
tags: col.storage.get_changed_tags(Usn(usn))?,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_config_json(&self, key: &str) -> Result<Vec<u8>> {
|
||||
self.with_col(|col| {
|
||||
let val: Option<JsonValue> = col.get_config_optional(key);
|
||||
match val {
|
||||
None => Ok(vec![]),
|
||||
Some(val) => Ok(serde_json::to_vec(&val)?),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_config_json(&self, input: pb::SetConfigJson) -> Result<()> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
if let Some(op) = input.op {
|
||||
match op {
|
||||
pb::set_config_json::Op::Val(val) => {
|
||||
// ensure it's a well-formed object
|
||||
let val: JsonValue = serde_json::from_slice(&val)?;
|
||||
col.set_config(input.key.as_str(), &val)
|
||||
}
|
||||
pb::set_config_json::Op::Remove(_) => col.remove_config(input.key.as_str()),
|
||||
}
|
||||
} else {
|
||||
Err(AnkiError::invalid_input("no op received"))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn set_all_config(&self, conf: &[u8]) -> Result<()> {
|
||||
let val: HashMap<String, JsonValue> = serde_json::from_slice(conf)?;
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
col.storage
|
||||
.set_all_config(val, col.usn()?, TimestampSecs::now())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_all_config(&self) -> Result<Vec<u8>> {
|
||||
self.with_col(|col| {
|
||||
let conf = col.storage.get_all_config()?;
|
||||
serde_json::to_vec(&conf).map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_preferences(&self) -> Result<pb::Preferences> {
|
||||
self.with_col(|col| col.get_preferences())
|
||||
}
|
||||
|
||||
fn set_preferences(&self, prefs: pb::Preferences) -> Result<()> {
|
||||
self.with_col(|col| col.transact(None, |col| col.set_preferences(prefs)))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_nids(ids: Vec<i64>) -> Vec<NoteID> {
|
||||
|
|
|
@ -93,6 +93,18 @@ fn want_release_gil(method: u32) -> bool {
|
|||
BackendMethod::CloseCollection => true,
|
||||
BackendMethod::AbortMediaSync => true,
|
||||
BackendMethod::BeforeUpload => true,
|
||||
BackendMethod::TranslateString => false,
|
||||
BackendMethod::FormatTimespan => false,
|
||||
BackendMethod::RegisterTags => true,
|
||||
BackendMethod::AllTags => true,
|
||||
BackendMethod::GetChangedTags => true,
|
||||
BackendMethod::GetConfigJson => true,
|
||||
BackendMethod::SetConfigJson => true,
|
||||
BackendMethod::RemoveConfig => true,
|
||||
BackendMethod::SetAllConfig => true,
|
||||
BackendMethod::GetAllConfig => true,
|
||||
BackendMethod::GetPreferences => true,
|
||||
BackendMethod::SetPreferences => true,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
@ -101,23 +113,12 @@ fn want_release_gil(method: u32) -> bool {
|
|||
|
||||
#[pymethods]
|
||||
impl Backend {
|
||||
fn command(&mut self, py: Python, input: &PyBytes, release_gil: bool) -> PyObject {
|
||||
let in_bytes = input.as_bytes();
|
||||
let out_bytes = if release_gil {
|
||||
py.allow_threads(move || self.backend.run_command_bytes(in_bytes))
|
||||
} else {
|
||||
self.backend.run_command_bytes(in_bytes)
|
||||
};
|
||||
let out_obj = PyBytes::new(py, &out_bytes);
|
||||
out_obj.into()
|
||||
}
|
||||
|
||||
fn command2(&mut self, py: Python, method: u32, input: &PyBytes) -> PyResult<PyObject> {
|
||||
fn command(&mut self, py: Python, method: u32, input: &PyBytes) -> PyResult<PyObject> {
|
||||
let in_bytes = input.as_bytes();
|
||||
if want_release_gil(method) {
|
||||
py.allow_threads(move || self.backend.run_command_bytes2(method, in_bytes))
|
||||
py.allow_threads(move || self.backend.run_command_bytes(method, in_bytes))
|
||||
} else {
|
||||
self.backend.run_command_bytes2(method, in_bytes)
|
||||
self.backend.run_command_bytes(method, in_bytes)
|
||||
}
|
||||
.map(|out_bytes| {
|
||||
let out_obj = PyBytes::new(py, &out_bytes);
|
||||
|
|
Loading…
Reference in a new issue