mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -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 SyncMedia (SyncMediaIn) returns (Empty);
|
||||||
rpc AbortMediaSync (Empty) returns (Empty);
|
rpc AbortMediaSync (Empty) returns (Empty);
|
||||||
rpc BeforeUpload (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
|
// Protobuf stored in .anki2 files
|
||||||
|
@ -398,49 +419,9 @@ message I18nBackendInit {
|
||||||
string locale_folder_path = 5;
|
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 {
|
message BackendError {
|
||||||
// localized error description suitable for displaying to the user
|
// localized error description suitable for displaying to the user
|
||||||
string localized = 1;
|
string localized = 1;
|
||||||
|
@ -504,6 +485,10 @@ message MediaSyncUploadProgress {
|
||||||
uint32 deletions = 2;
|
uint32 deletions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
message SchedTimingTodayOut {
|
message SchedTimingTodayOut {
|
||||||
uint32 days_elapsed = 1;
|
uint32 days_elapsed = 1;
|
||||||
int64 next_day_at = 2;
|
int64 next_day_at = 2;
|
||||||
|
@ -632,7 +617,7 @@ message TranslateArgValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message FormatTimeSpanIn {
|
message FormatTimespanIn {
|
||||||
enum Context {
|
enum Context {
|
||||||
PRECISE = 0;
|
PRECISE = 0;
|
||||||
ANSWER_BUTTONS = 1;
|
ANSWER_BUTTONS = 1;
|
||||||
|
@ -736,12 +721,9 @@ message GetChangedTagsOut {
|
||||||
repeated string tags = 1;
|
repeated string tags = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetConfigJson {
|
message SetConfigJsonIn {
|
||||||
string key = 1;
|
string key = 1;
|
||||||
oneof op {
|
bytes val = 2;
|
||||||
bytes val = 2;
|
|
||||||
Empty remove = 3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StockNoteType {
|
enum StockNoteType {
|
||||||
|
|
|
@ -26,7 +26,13 @@ from anki.lang import _
|
||||||
from anki.media import MediaManager, media_paths_from_col_path
|
from anki.media import MediaManager, media_paths_from_col_path
|
||||||
from anki.models import ModelManager
|
from anki.models import ModelManager
|
||||||
from anki.notes import Note
|
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.sched import Scheduler as V1Scheduler
|
||||||
from anki.schedv2 import Scheduler as V2Scheduler
|
from anki.schedv2 import Scheduler as V2Scheduler
|
||||||
from anki.tags import TagManager
|
from anki.tags import TagManager
|
||||||
|
@ -65,13 +71,23 @@ class Collection:
|
||||||
n = os.path.splitext(os.path.basename(self.path))[0]
|
n = os.path.splitext(os.path.basename(self.path))[0]
|
||||||
return n
|
return n
|
||||||
|
|
||||||
def tr(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
|
||||||
return self.backend.translate(key, **kwargs)
|
|
||||||
|
|
||||||
def weakref(self) -> Collection:
|
def weakref(self) -> Collection:
|
||||||
"Shortcut to create a weak reference that doesn't break code completion."
|
"Shortcut to create a weak reference that doesn't break code completion."
|
||||||
return weakref.proxy(self)
|
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
|
# Scheduler
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import weakref
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
|
from anki.rsbackend import NotFoundError, from_json_bytes, to_json_bytes
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
|
@ -30,10 +31,13 @@ class ConfigManager:
|
||||||
self.col = col.weakref()
|
self.col = col.weakref()
|
||||||
|
|
||||||
def get_immutable(self, key: str) -> Any:
|
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:
|
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:
|
def remove(self, key: str) -> None:
|
||||||
self.col.backend.remove_config(key)
|
self.col.backend.remove_config(key)
|
||||||
|
|
|
@ -153,7 +153,7 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
||||||
|
|
||||||
MediaSyncProgress = pb.MediaSyncProgress
|
MediaSyncProgress = pb.MediaSyncProgress
|
||||||
|
|
||||||
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
|
FormatTimeSpanContext = pb.FormatTimespanIn.Context
|
||||||
|
|
||||||
|
|
||||||
class ProgressKind(enum.Enum):
|
class ProgressKind(enum.Enum):
|
||||||
|
@ -203,35 +203,6 @@ class RustBackend:
|
||||||
self._backend = ankirspy.open_backend(init_msg.SerializeToString())
|
self._backend = ankirspy.open_backend(init_msg.SerializeToString())
|
||||||
self._backend.set_progress_callback(_on_progress)
|
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(
|
def db_query(
|
||||||
self, sql: str, args: Sequence[ValueForDB], first_row_only: bool
|
self, sql: str, args: Sequence[ValueForDB], first_row_only: bool
|
||||||
) -> List[DBRow]:
|
) -> List[DBRow]:
|
||||||
|
@ -254,76 +225,23 @@ class RustBackend:
|
||||||
def _db_command(self, input: Dict[str, Any]) -> Any:
|
def _db_command(self, input: Dict[str, Any]) -> Any:
|
||||||
return orjson.loads(self._backend.db_command(orjson.dumps(input)))
|
return orjson.loads(self._backend.db_command(orjson.dumps(input)))
|
||||||
|
|
||||||
def all_tags(self) -> Iterable[TagUsnTuple]:
|
def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str:
|
||||||
return self._run_command(pb.BackendInput(all_tags=pb.Empty())).all_tags.tags
|
return self.translate_string(translate_string_in(key, **kwargs))
|
||||||
|
|
||||||
def register_tags(self, tags: str, usn: Optional[int], clear_first: bool) -> bool:
|
def format_time_span(
|
||||||
if usn is None:
|
self,
|
||||||
preserve_usn = False
|
seconds: float,
|
||||||
usn_ = 0
|
context: FormatTimeSpanContext = FormatTimeSpanContext.INTERVALS,
|
||||||
else:
|
) -> str:
|
||||||
usn_ = usn
|
print(
|
||||||
preserve_usn = True
|
"please use col.format_timespan() instead of col.backend.format_time_span()"
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
return self.format_timespan(seconds, context)
|
||||||
|
|
||||||
def get_config_json(self, key: str) -> Any:
|
def _run_command(self, method: int, input: Any) -> bytes:
|
||||||
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:
|
|
||||||
input_bytes = input.SerializeToString()
|
input_bytes = input.SerializeToString()
|
||||||
try:
|
try:
|
||||||
return self._backend.command2(method, input_bytes)
|
return self._backend.command(method, input_bytes)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_bytes = bytes(e.args[0])
|
err_bytes = bytes(e.args[0])
|
||||||
err = pb.BackendError()
|
err = pb.BackendError()
|
||||||
|
@ -338,7 +256,7 @@ class RustBackend:
|
||||||
def extract_av_tags(self, text: str, question_side: bool) -> pb.ExtractAVTagsOut:
|
def extract_av_tags(self, text: str, question_side: bool) -> pb.ExtractAVTagsOut:
|
||||||
input = pb.ExtractAVTagsIn(text=text, question_side=question_side)
|
input = pb.ExtractAVTagsIn(text=text, question_side=question_side)
|
||||||
output = pb.ExtractAVTagsOut()
|
output = pb.ExtractAVTagsOut()
|
||||||
output.ParseFromString(self._run_command2(1, input))
|
output.ParseFromString(self._run_command(1, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def extract_latex(
|
def extract_latex(
|
||||||
|
@ -346,19 +264,19 @@ class RustBackend:
|
||||||
) -> pb.ExtractLatexOut:
|
) -> pb.ExtractLatexOut:
|
||||||
input = pb.ExtractLatexIn(text=text, svg=svg, expand_clozes=expand_clozes)
|
input = pb.ExtractLatexIn(text=text, svg=svg, expand_clozes=expand_clozes)
|
||||||
output = pb.ExtractLatexOut()
|
output = pb.ExtractLatexOut()
|
||||||
output.ParseFromString(self._run_command2(2, input))
|
output.ParseFromString(self._run_command(2, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_empty_cards(self) -> pb.EmptyCardsReport:
|
def get_empty_cards(self) -> pb.EmptyCardsReport:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.EmptyCardsReport()
|
output = pb.EmptyCardsReport()
|
||||||
output.ParseFromString(self._run_command2(3, input))
|
output.ParseFromString(self._run_command(3, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def render_existing_card(self, card_id: int, browser: bool) -> pb.RenderCardOut:
|
def render_existing_card(self, card_id: int, browser: bool) -> pb.RenderCardOut:
|
||||||
input = pb.RenderExistingCardIn(card_id=card_id, browser=browser)
|
input = pb.RenderExistingCardIn(card_id=card_id, browser=browser)
|
||||||
output = pb.RenderCardOut()
|
output = pb.RenderCardOut()
|
||||||
output.ParseFromString(self._run_command2(4, input))
|
output.ParseFromString(self._run_command(4, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def render_uncommitted_card(
|
def render_uncommitted_card(
|
||||||
|
@ -368,25 +286,25 @@ class RustBackend:
|
||||||
note=note, card_ord=card_ord, template=template, fill_empty=fill_empty
|
note=note, card_ord=card_ord, template=template, fill_empty=fill_empty
|
||||||
)
|
)
|
||||||
output = pb.RenderCardOut()
|
output = pb.RenderCardOut()
|
||||||
output.ParseFromString(self._run_command2(5, input))
|
output.ParseFromString(self._run_command(5, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def strip_av_tags(self, val: str) -> str:
|
def strip_av_tags(self, val: str) -> str:
|
||||||
input = pb.String(val=val)
|
input = pb.String(val=val)
|
||||||
output = pb.String()
|
output = pb.String()
|
||||||
output.ParseFromString(self._run_command2(6, input))
|
output.ParseFromString(self._run_command(6, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def search_cards(self, search: str, order: pb.SortOrder) -> Sequence[int]:
|
def search_cards(self, search: str, order: pb.SortOrder) -> Sequence[int]:
|
||||||
input = pb.SearchCardsIn(search=search, order=order)
|
input = pb.SearchCardsIn(search=search, order=order)
|
||||||
output = pb.SearchCardsOut()
|
output = pb.SearchCardsOut()
|
||||||
output.ParseFromString(self._run_command2(7, input))
|
output.ParseFromString(self._run_command(7, input))
|
||||||
return output.card_ids
|
return output.card_ids
|
||||||
|
|
||||||
def search_notes(self, search: str) -> Sequence[int]:
|
def search_notes(self, search: str) -> Sequence[int]:
|
||||||
input = pb.SearchNotesIn(search=search)
|
input = pb.SearchNotesIn(search=search)
|
||||||
output = pb.SearchNotesOut()
|
output = pb.SearchNotesOut()
|
||||||
output.ParseFromString(self._run_command2(8, input))
|
output.ParseFromString(self._run_command(8, input))
|
||||||
return output.note_ids
|
return output.note_ids
|
||||||
|
|
||||||
def find_and_replace(
|
def find_and_replace(
|
||||||
|
@ -407,67 +325,67 @@ class RustBackend:
|
||||||
field_name=field_name,
|
field_name=field_name,
|
||||||
)
|
)
|
||||||
output = pb.UInt32()
|
output = pb.UInt32()
|
||||||
output.ParseFromString(self._run_command2(9, input))
|
output.ParseFromString(self._run_command(9, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def local_minutes_west(self, val: int) -> int:
|
def local_minutes_west(self, val: int) -> int:
|
||||||
input = pb.Int64(val=val)
|
input = pb.Int64(val=val)
|
||||||
output = pb.Int32()
|
output = pb.Int32()
|
||||||
output.ParseFromString(self._run_command2(10, input))
|
output.ParseFromString(self._run_command(10, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def set_local_minutes_west(self, val: int) -> pb.Empty:
|
def set_local_minutes_west(self, val: int) -> pb.Empty:
|
||||||
input = pb.Int32(val=val)
|
input = pb.Int32(val=val)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(11, input))
|
output.ParseFromString(self._run_command(11, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def sched_timing_today(self) -> pb.SchedTimingTodayOut:
|
def sched_timing_today(self) -> pb.SchedTimingTodayOut:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.SchedTimingTodayOut()
|
output = pb.SchedTimingTodayOut()
|
||||||
output.ParseFromString(self._run_command2(12, input))
|
output.ParseFromString(self._run_command(12, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def studied_today(self, cards: int, seconds: float) -> str:
|
def studied_today(self, cards: int, seconds: float) -> str:
|
||||||
input = pb.StudiedTodayIn(cards=cards, seconds=seconds)
|
input = pb.StudiedTodayIn(cards=cards, seconds=seconds)
|
||||||
output = pb.String()
|
output = pb.String()
|
||||||
output.ParseFromString(self._run_command2(13, input))
|
output.ParseFromString(self._run_command(13, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def congrats_learn_message(self, next_due: float, remaining: int) -> str:
|
def congrats_learn_message(self, next_due: float, remaining: int) -> str:
|
||||||
input = pb.CongratsLearnMessageIn(next_due=next_due, remaining=remaining)
|
input = pb.CongratsLearnMessageIn(next_due=next_due, remaining=remaining)
|
||||||
output = pb.String()
|
output = pb.String()
|
||||||
output.ParseFromString(self._run_command2(14, input))
|
output.ParseFromString(self._run_command(14, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def check_media(self) -> pb.CheckMediaOut:
|
def check_media(self) -> pb.CheckMediaOut:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.CheckMediaOut()
|
output = pb.CheckMediaOut()
|
||||||
output.ParseFromString(self._run_command2(15, input))
|
output.ParseFromString(self._run_command(15, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def trash_media_files(self, fnames: Sequence[str]) -> pb.Empty:
|
def trash_media_files(self, fnames: Sequence[str]) -> pb.Empty:
|
||||||
input = pb.TrashMediaFilesIn(fnames=fnames)
|
input = pb.TrashMediaFilesIn(fnames=fnames)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(16, input))
|
output.ParseFromString(self._run_command(16, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_media_file(self, desired_name: str, data: bytes) -> str:
|
def add_media_file(self, desired_name: str, data: bytes) -> str:
|
||||||
input = pb.AddMediaFileIn(desired_name=desired_name, data=data)
|
input = pb.AddMediaFileIn(desired_name=desired_name, data=data)
|
||||||
output = pb.String()
|
output = pb.String()
|
||||||
output.ParseFromString(self._run_command2(17, input))
|
output.ParseFromString(self._run_command(17, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def empty_trash(self) -> pb.Empty:
|
def empty_trash(self) -> pb.Empty:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(18, input))
|
output.ParseFromString(self._run_command(18, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def restore_trash(self) -> pb.Empty:
|
def restore_trash(self) -> pb.Empty:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(19, input))
|
output.ParseFromString(self._run_command(19, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_or_update_deck_legacy(
|
def add_or_update_deck_legacy(
|
||||||
|
@ -477,37 +395,37 @@ class RustBackend:
|
||||||
deck=deck, preserve_usn_and_mtime=preserve_usn_and_mtime
|
deck=deck, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||||
)
|
)
|
||||||
output = pb.DeckID()
|
output = pb.DeckID()
|
||||||
output.ParseFromString(self._run_command2(20, input))
|
output.ParseFromString(self._run_command(20, input))
|
||||||
return output.did
|
return output.did
|
||||||
|
|
||||||
def deck_tree(self, include_counts: bool, top_deck_id: int) -> pb.DeckTreeNode:
|
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)
|
input = pb.DeckTreeIn(include_counts=include_counts, top_deck_id=top_deck_id)
|
||||||
output = pb.DeckTreeNode()
|
output = pb.DeckTreeNode()
|
||||||
output.ParseFromString(self._run_command2(21, input))
|
output.ParseFromString(self._run_command(21, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def deck_tree_legacy(self) -> bytes:
|
def deck_tree_legacy(self) -> bytes:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(22, input))
|
output.ParseFromString(self._run_command(22, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_all_decks_legacy(self) -> bytes:
|
def get_all_decks_legacy(self) -> bytes:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(23, input))
|
output.ParseFromString(self._run_command(23, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_deck_id_by_name(self, val: str) -> int:
|
def get_deck_id_by_name(self, val: str) -> int:
|
||||||
input = pb.String(val=val)
|
input = pb.String(val=val)
|
||||||
output = pb.DeckID()
|
output = pb.DeckID()
|
||||||
output.ParseFromString(self._run_command2(24, input))
|
output.ParseFromString(self._run_command(24, input))
|
||||||
return output.did
|
return output.did
|
||||||
|
|
||||||
def get_deck_legacy(self, did: int) -> bytes:
|
def get_deck_legacy(self, did: int) -> bytes:
|
||||||
input = pb.DeckID(did=did)
|
input = pb.DeckID(did=did)
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(25, input))
|
output.ParseFromString(self._run_command(25, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_deck_names(
|
def get_deck_names(
|
||||||
|
@ -517,19 +435,19 @@ class RustBackend:
|
||||||
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
||||||
)
|
)
|
||||||
output = pb.DeckNames()
|
output = pb.DeckNames()
|
||||||
output.ParseFromString(self._run_command2(26, input))
|
output.ParseFromString(self._run_command(26, input))
|
||||||
return output.entries
|
return output.entries
|
||||||
|
|
||||||
def new_deck_legacy(self, val: bool) -> bytes:
|
def new_deck_legacy(self, val: bool) -> bytes:
|
||||||
input = pb.Bool(val=val)
|
input = pb.Bool(val=val)
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(27, input))
|
output.ParseFromString(self._run_command(27, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def remove_deck(self, did: int) -> pb.Empty:
|
def remove_deck(self, did: int) -> pb.Empty:
|
||||||
input = pb.DeckID(did=did)
|
input = pb.DeckID(did=did)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(28, input))
|
output.ParseFromString(self._run_command(28, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_or_update_deck_config_legacy(
|
def add_or_update_deck_config_legacy(
|
||||||
|
@ -539,76 +457,76 @@ class RustBackend:
|
||||||
config=config, preserve_usn_and_mtime=preserve_usn_and_mtime
|
config=config, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||||
)
|
)
|
||||||
output = pb.DeckConfigID()
|
output = pb.DeckConfigID()
|
||||||
output.ParseFromString(self._run_command2(29, input))
|
output.ParseFromString(self._run_command(29, input))
|
||||||
return output.dcid
|
return output.dcid
|
||||||
|
|
||||||
def all_deck_config_legacy(self) -> bytes:
|
def all_deck_config_legacy(self) -> bytes:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(30, input))
|
output.ParseFromString(self._run_command(30, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_deck_config_legacy(self, dcid: int) -> bytes:
|
def get_deck_config_legacy(self, dcid: int) -> bytes:
|
||||||
input = pb.DeckConfigID(dcid=dcid)
|
input = pb.DeckConfigID(dcid=dcid)
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(31, input))
|
output.ParseFromString(self._run_command(31, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def new_deck_config_legacy(self) -> bytes:
|
def new_deck_config_legacy(self) -> bytes:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(32, input))
|
output.ParseFromString(self._run_command(32, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def remove_deck_config(self, dcid: int) -> pb.Empty:
|
def remove_deck_config(self, dcid: int) -> pb.Empty:
|
||||||
input = pb.DeckConfigID(dcid=dcid)
|
input = pb.DeckConfigID(dcid=dcid)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(33, input))
|
output.ParseFromString(self._run_command(33, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_card(self, cid: int) -> pb.Card:
|
def get_card(self, cid: int) -> pb.Card:
|
||||||
input = pb.CardID(cid=cid)
|
input = pb.CardID(cid=cid)
|
||||||
output = pb.Card()
|
output = pb.Card()
|
||||||
output.ParseFromString(self._run_command2(34, input))
|
output.ParseFromString(self._run_command(34, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def update_card(self, input: pb.Card) -> pb.Empty:
|
def update_card(self, input: pb.Card) -> pb.Empty:
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(35, input))
|
output.ParseFromString(self._run_command(35, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_card(self, input: pb.Card) -> int:
|
def add_card(self, input: pb.Card) -> int:
|
||||||
output = pb.CardID()
|
output = pb.CardID()
|
||||||
output.ParseFromString(self._run_command2(36, input))
|
output.ParseFromString(self._run_command(36, input))
|
||||||
return output.cid
|
return output.cid
|
||||||
|
|
||||||
def new_note(self, ntid: int) -> pb.Note:
|
def new_note(self, ntid: int) -> pb.Note:
|
||||||
input = pb.NoteTypeID(ntid=ntid)
|
input = pb.NoteTypeID(ntid=ntid)
|
||||||
output = pb.Note()
|
output = pb.Note()
|
||||||
output.ParseFromString(self._run_command2(37, input))
|
output.ParseFromString(self._run_command(37, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_note(self, note: pb.Note, deck_id: int) -> int:
|
def add_note(self, note: pb.Note, deck_id: int) -> int:
|
||||||
input = pb.AddNoteIn(note=note, deck_id=deck_id)
|
input = pb.AddNoteIn(note=note, deck_id=deck_id)
|
||||||
output = pb.NoteID()
|
output = pb.NoteID()
|
||||||
output.ParseFromString(self._run_command2(38, input))
|
output.ParseFromString(self._run_command(38, input))
|
||||||
return output.nid
|
return output.nid
|
||||||
|
|
||||||
def update_note(self, input: pb.Note) -> pb.Empty:
|
def update_note(self, input: pb.Note) -> pb.Empty:
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(39, input))
|
output.ParseFromString(self._run_command(39, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_note(self, nid: int) -> pb.Note:
|
def get_note(self, nid: int) -> pb.Note:
|
||||||
input = pb.NoteID(nid=nid)
|
input = pb.NoteID(nid=nid)
|
||||||
output = pb.Note()
|
output = pb.Note()
|
||||||
output.ParseFromString(self._run_command2(40, input))
|
output.ParseFromString(self._run_command(40, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_note_tags(self, nids: Sequence[int], tags: str) -> int:
|
def add_note_tags(self, nids: Sequence[int], tags: str) -> int:
|
||||||
input = pb.AddNoteTagsIn(nids=nids, tags=tags)
|
input = pb.AddNoteTagsIn(nids=nids, tags=tags)
|
||||||
output = pb.UInt32()
|
output = pb.UInt32()
|
||||||
output.ParseFromString(self._run_command2(41, input))
|
output.ParseFromString(self._run_command(41, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def update_note_tags(
|
def update_note_tags(
|
||||||
|
@ -618,12 +536,12 @@ class RustBackend:
|
||||||
nids=nids, tags=tags, replacement=replacement, regex=regex
|
nids=nids, tags=tags, replacement=replacement, regex=regex
|
||||||
)
|
)
|
||||||
output = pb.UInt32()
|
output = pb.UInt32()
|
||||||
output.ParseFromString(self._run_command2(42, input))
|
output.ParseFromString(self._run_command(42, input))
|
||||||
return output.val
|
return output.val
|
||||||
|
|
||||||
def cloze_numbers_in_note(self, input: pb.Note) -> Sequence[int]:
|
def cloze_numbers_in_note(self, input: pb.Note) -> Sequence[int]:
|
||||||
output = pb.ClozeNumbersInNoteOut()
|
output = pb.ClozeNumbersInNoteOut()
|
||||||
output.ParseFromString(self._run_command2(43, input))
|
output.ParseFromString(self._run_command(43, input))
|
||||||
return output.numbers
|
return output.numbers
|
||||||
|
|
||||||
def after_note_updates(
|
def after_note_updates(
|
||||||
|
@ -635,13 +553,13 @@ class RustBackend:
|
||||||
generate_cards=generate_cards,
|
generate_cards=generate_cards,
|
||||||
)
|
)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(44, input))
|
output.ParseFromString(self._run_command(44, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def field_names_for_notes(self, nids: Sequence[int]) -> Sequence[str]:
|
def field_names_for_notes(self, nids: Sequence[int]) -> Sequence[str]:
|
||||||
input = pb.FieldNamesForNotesIn(nids=nids)
|
input = pb.FieldNamesForNotesIn(nids=nids)
|
||||||
output = pb.FieldNamesForNotesOut()
|
output = pb.FieldNamesForNotesOut()
|
||||||
output.ParseFromString(self._run_command2(45, input))
|
output.ParseFromString(self._run_command(45, input))
|
||||||
return output.fields
|
return output.fields
|
||||||
|
|
||||||
def add_or_update_notetype(self, json: bytes, preserve_usn_and_mtime: bool) -> int:
|
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
|
json=json, preserve_usn_and_mtime=preserve_usn_and_mtime
|
||||||
)
|
)
|
||||||
output = pb.NoteTypeID()
|
output = pb.NoteTypeID()
|
||||||
output.ParseFromString(self._run_command2(46, input))
|
output.ParseFromString(self._run_command(46, input))
|
||||||
return output.ntid
|
return output.ntid
|
||||||
|
|
||||||
def get_stock_notetype_legacy(self, kind: pb.StockNoteType) -> bytes:
|
def get_stock_notetype_legacy(self, kind: pb.StockNoteType) -> bytes:
|
||||||
input = pb.GetStockNotetypeIn(kind=kind)
|
input = pb.GetStockNotetypeIn(kind=kind)
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(47, input))
|
output.ParseFromString(self._run_command(47, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_notetype_legacy(self, ntid: int) -> bytes:
|
def get_notetype_legacy(self, ntid: int) -> bytes:
|
||||||
input = pb.NoteTypeID(ntid=ntid)
|
input = pb.NoteTypeID(ntid=ntid)
|
||||||
output = pb.Json()
|
output = pb.Json()
|
||||||
output.ParseFromString(self._run_command2(48, input))
|
output.ParseFromString(self._run_command(48, input))
|
||||||
return output.json
|
return output.json
|
||||||
|
|
||||||
def get_notetype_names(self) -> Sequence[pb.NoteTypeNameID]:
|
def get_notetype_names(self) -> Sequence[pb.NoteTypeNameID]:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.NoteTypeNames()
|
output = pb.NoteTypeNames()
|
||||||
output.ParseFromString(self._run_command2(49, input))
|
output.ParseFromString(self._run_command(49, input))
|
||||||
return output.entries
|
return output.entries
|
||||||
|
|
||||||
def get_notetype_names_and_counts(self) -> Sequence[pb.NoteTypeNameIDUseCount]:
|
def get_notetype_names_and_counts(self) -> Sequence[pb.NoteTypeNameIDUseCount]:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.NoteTypeUseCounts()
|
output = pb.NoteTypeUseCounts()
|
||||||
output.ParseFromString(self._run_command2(50, input))
|
output.ParseFromString(self._run_command(50, input))
|
||||||
return output.entries
|
return output.entries
|
||||||
|
|
||||||
def get_notetype_id_by_name(self, val: str) -> int:
|
def get_notetype_id_by_name(self, val: str) -> int:
|
||||||
input = pb.String(val=val)
|
input = pb.String(val=val)
|
||||||
output = pb.NoteTypeID()
|
output = pb.NoteTypeID()
|
||||||
output.ParseFromString(self._run_command2(51, input))
|
output.ParseFromString(self._run_command(51, input))
|
||||||
return output.ntid
|
return output.ntid
|
||||||
|
|
||||||
def remove_notetype(self, ntid: int) -> pb.Empty:
|
def remove_notetype(self, ntid: int) -> pb.Empty:
|
||||||
input = pb.NoteTypeID(ntid=ntid)
|
input = pb.NoteTypeID(ntid=ntid)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(52, input))
|
output.ParseFromString(self._run_command(52, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def open_collection(
|
def open_collection(
|
||||||
|
@ -702,37 +620,114 @@ class RustBackend:
|
||||||
log_path=log_path,
|
log_path=log_path,
|
||||||
)
|
)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(53, input))
|
output.ParseFromString(self._run_command(53, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def close_collection(self, downgrade_to_schema11: bool) -> pb.Empty:
|
def close_collection(self, downgrade_to_schema11: bool) -> pb.Empty:
|
||||||
input = pb.CloseCollectionIn(downgrade_to_schema11=downgrade_to_schema11)
|
input = pb.CloseCollectionIn(downgrade_to_schema11=downgrade_to_schema11)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(54, input))
|
output.ParseFromString(self._run_command(54, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def check_database(self) -> Sequence[str]:
|
def check_database(self) -> Sequence[str]:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.CheckDatabaseOut()
|
output = pb.CheckDatabaseOut()
|
||||||
output.ParseFromString(self._run_command2(55, input))
|
output.ParseFromString(self._run_command(55, input))
|
||||||
return output.problems
|
return output.problems
|
||||||
|
|
||||||
def sync_media(self, hkey: str, endpoint: str) -> pb.Empty:
|
def sync_media(self, hkey: str, endpoint: str) -> pb.Empty:
|
||||||
input = pb.SyncMediaIn(hkey=hkey, endpoint=endpoint)
|
input = pb.SyncMediaIn(hkey=hkey, endpoint=endpoint)
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(56, input))
|
output.ParseFromString(self._run_command(56, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def abort_media_sync(self) -> pb.Empty:
|
def abort_media_sync(self) -> pb.Empty:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = pb.Empty()
|
output = pb.Empty()
|
||||||
output.ParseFromString(self._run_command2(57, input))
|
output.ParseFromString(self._run_command(57, input))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def before_upload(self) -> pb.Empty:
|
def before_upload(self) -> pb.Empty:
|
||||||
input = pb.Empty()
|
input = pb.Empty()
|
||||||
output = 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
|
return output
|
||||||
|
|
||||||
# @@AUTOGEN@@
|
# @@AUTOGEN@@
|
||||||
|
|
|
@ -1369,9 +1369,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
ivl_secs = self.nextIvl(card, ease)
|
ivl_secs = self.nextIvl(card, ease)
|
||||||
if not ivl_secs:
|
if not ivl_secs:
|
||||||
return _("(end)")
|
return _("(end)")
|
||||||
s = self.col.backend.format_time_span(
|
s = self.col.format_timespan(ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS)
|
||||||
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
|
|
||||||
)
|
|
||||||
if ivl_secs < self.col.conf["collapseTime"]:
|
if ivl_secs < self.col.conf["collapseTime"]:
|
||||||
s = "<" + s
|
s = "<" + s
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -52,9 +52,7 @@ class CardStats:
|
||||||
if next:
|
if next:
|
||||||
self.addLine(self.col.tr(TR.STATISTICS_DUE_DATE), next)
|
self.addLine(self.col.tr(TR.STATISTICS_DUE_DATE), next)
|
||||||
if c.queue == QUEUE_TYPE_REV:
|
if c.queue == QUEUE_TYPE_REV:
|
||||||
self.addLine(
|
self.addLine(_("Interval"), self.col.format_timespan(c.ivl * 86400))
|
||||||
_("Interval"), self.col.backend.format_time_span(c.ivl * 86400)
|
|
||||||
)
|
|
||||||
self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0))
|
self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0))
|
||||||
self.addLine(_("Reviews"), "%d" % c.reps)
|
self.addLine(_("Reviews"), "%d" % c.reps)
|
||||||
self.addLine(_("Lapses"), "%d" % c.lapses)
|
self.addLine(_("Lapses"), "%d" % c.lapses)
|
||||||
|
@ -86,9 +84,7 @@ class CardStats:
|
||||||
return time.strftime("%Y-%m-%d", time.localtime(tm))
|
return time.strftime("%Y-%m-%d", time.localtime(tm))
|
||||||
|
|
||||||
def time(self, tm: float) -> str:
|
def time(self, tm: float) -> str:
|
||||||
return self.col.backend.format_time_span(
|
return self.col.format_timespan(tm, context=FormatTimeSpanContext.PRECISE)
|
||||||
tm, context=FormatTimeSpanContext.PRECISE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Collection stats
|
# Collection stats
|
||||||
|
@ -645,12 +641,8 @@ group by day order by day)"""
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
i: List[str] = []
|
i: List[str] = []
|
||||||
self._line(
|
self._line(i, _("Average interval"), self.col.format_timespan(avg * 86400))
|
||||||
i, _("Average interval"), self.col.backend.format_time_span(avg * 86400)
|
self._line(i, _("Longest interval"), self.col.format_timespan(max_ * 86400))
|
||||||
)
|
|
||||||
self._line(
|
|
||||||
i, _("Longest interval"), self.col.backend.format_time_span(max_ * 86400)
|
|
||||||
)
|
|
||||||
return txt + self._lineTbl(i)
|
return txt + self._lineTbl(i)
|
||||||
|
|
||||||
def _ivls(self) -> Tuple[List[Any], int]:
|
def _ivls(self) -> Tuple[List[Any], int]:
|
||||||
|
|
|
@ -19,6 +19,8 @@ from . import hooks
|
||||||
from .httpclient import HttpClient
|
from .httpclient import HttpClient
|
||||||
|
|
||||||
# add-on compat
|
# add-on compat
|
||||||
|
from .rsbackend import from_json_bytes, to_json_bytes
|
||||||
|
|
||||||
AnkiRequestsClient = HttpClient
|
AnkiRequestsClient = HttpClient
|
||||||
|
|
||||||
|
|
||||||
|
@ -402,7 +404,7 @@ from notes where %s"""
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def getTags(self) -> List:
|
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:
|
def mergeTags(self, tags) -> None:
|
||||||
self.col.tags.register(tags, usn=self.maxUsn)
|
self.col.tags.register(tags, usn=self.maxUsn)
|
||||||
|
@ -448,10 +450,10 @@ from notes where %s"""
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def getConf(self) -> Dict[str, Any]:
|
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:
|
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
|
# HTTP syncing tools
|
||||||
|
|
|
@ -36,7 +36,16 @@ class TagManager:
|
||||||
def register(
|
def register(
|
||||||
self, tags: Collection[str], usn: Optional[int] = None, clear=False
|
self, tags: Collection[str], usn: Optional[int] = None, clear=False
|
||||||
) -> None:
|
) -> 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:
|
def registerNotes(self, nids: Optional[List[int]] = None) -> None:
|
||||||
"Add any missing tags from notes to the tags list."
|
"Add any missing tags from notes to the tags list."
|
||||||
|
|
|
@ -29,6 +29,9 @@ LABEL_OPTIONAL = 1
|
||||||
LABEL_REQUIRED = 2
|
LABEL_REQUIRED = 2
|
||||||
LABEL_REPEATED = 3
|
LABEL_REPEATED = 3
|
||||||
|
|
||||||
|
# messages we don't want to unroll in codegen
|
||||||
|
SKIP_UNROLL_INPUT = {"TranslateString"}
|
||||||
|
|
||||||
|
|
||||||
def python_type(field):
|
def python_type(field):
|
||||||
type = python_type_inner(field)
|
type = python_type_inner(field)
|
||||||
|
@ -50,13 +53,20 @@ def python_type_inner(field):
|
||||||
elif type == TYPE_BYTES:
|
elif type == TYPE_BYTES:
|
||||||
return "bytes"
|
return "bytes"
|
||||||
elif type == TYPE_MESSAGE:
|
elif type == TYPE_MESSAGE:
|
||||||
return "pb." + field.message_type.name
|
return fullname(field.message_type.full_name)
|
||||||
elif type == TYPE_ENUM:
|
elif type == TYPE_ENUM:
|
||||||
return "pb." + field.enum_type.name
|
return fullname(field.enum_type.full_name)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"unknown type: {type}")
|
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
|
# get_deck_i_d -> get_deck_id etc
|
||||||
def fix_snakecase(name):
|
def fix_snakecase(name):
|
||||||
for fix in "a_v", "i_d":
|
for fix in "a_v", "i_d":
|
||||||
|
@ -80,14 +90,18 @@ def get_input_assign(msg):
|
||||||
|
|
||||||
def render_method(method, idx):
|
def render_method(method, idx):
|
||||||
input_name = method.input_type.name
|
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_args = get_input_args(method.input_type)
|
||||||
input_assign = get_input_assign(method.input_type)
|
input_assign = get_input_assign(method.input_type)
|
||||||
input_assign_outer = (
|
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:
|
else:
|
||||||
input_args = f"self, input: pb.{input_name}"
|
input_args = f"self, input: {fullname(method.input_type.full_name)}"
|
||||||
input_assign_outer = ""
|
input_assign_outer = ""
|
||||||
name = fix_snakecase(stringcase.snakecase(method.name))
|
name = fix_snakecase(stringcase.snakecase(method.name))
|
||||||
if len(method.output_type.fields) == 1:
|
if len(method.output_type.fields) == 1:
|
||||||
|
@ -101,7 +115,7 @@ def render_method(method, idx):
|
||||||
return f"""\
|
return f"""\
|
||||||
def {name}({input_args}) -> {return_type}:
|
def {name}({input_args}) -> {return_type}:
|
||||||
{input_assign_outer}output = pb.{method.output_type.name}()
|
{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}
|
return output{single_field}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -323,7 +323,7 @@ class DataModel(QAbstractTableModel):
|
||||||
return _("(new)")
|
return _("(new)")
|
||||||
elif c.type == CARD_TYPE_LRN:
|
elif c.type == CARD_TYPE_LRN:
|
||||||
return _("(learning)")
|
return _("(learning)")
|
||||||
return self.col.backend.format_time_span(c.ivl * 86400)
|
return self.col.format_timespan(c.ivl * 86400)
|
||||||
elif type == "cardEase":
|
elif type == "cardEase":
|
||||||
if c.type == CARD_TYPE_NEW:
|
if c.type == CARD_TYPE_NEW:
|
||||||
return _("(new)")
|
return _("(new)")
|
||||||
|
@ -1491,7 +1491,7 @@ border: 1px solid #000; padding: 3px; '>%s</div>"""
|
||||||
|
|
||||||
s += ("<td align=right>%s</td>" * 2) % (
|
s += ("<td align=right>%s</td>" * 2) % (
|
||||||
"%d%%" % (factor / 10) if factor else "",
|
"%d%%" % (factor / 10) if factor else "",
|
||||||
self.col.backend.format_time_span(taken),
|
self.col.format_timespan(taken),
|
||||||
) + "</tr>"
|
) + "</tr>"
|
||||||
s += "</table>"
|
s += "</table>"
|
||||||
if cnt < self.card.reps:
|
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):
|
def fmtTimeSpan(time, pad=0, point=0, short=False, inTime=False, unit=99):
|
||||||
print("fmtTimeSpan() has become col.backend.format_time_span()")
|
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:
|
def install_pylib_legacy() -> None:
|
||||||
|
|
|
@ -41,7 +41,7 @@ use crate::{
|
||||||
use fluent::FluentValue;
|
use fluent::FluentValue;
|
||||||
use futures::future::{AbortHandle, Abortable};
|
use futures::future::{AbortHandle, Abortable};
|
||||||
use log::error;
|
use log::error;
|
||||||
use pb::{backend_input::Value, BackendService};
|
use pb::BackendService;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use std::collections::{HashMap, HashSet};
|
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 {
|
impl std::convert::From<NetworkErrorKind> for i32 {
|
||||||
fn from(e: NetworkErrorKind) -> Self {
|
fn from(e: NetworkErrorKind) -> Self {
|
||||||
use pb::network_error::NetworkErrorKind as V;
|
use pb::network_error::NetworkErrorKind as V;
|
||||||
|
@ -947,6 +940,133 @@ impl BackendService for Backend {
|
||||||
fn before_upload(&mut self, _input: Empty) -> BackendResult<Empty> {
|
fn before_upload(&mut self, _input: Empty) -> BackendResult<Empty> {
|
||||||
self.with_col(|col| col.before_upload().map(Into::into))
|
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 {
|
impl Backend {
|
||||||
|
@ -964,30 +1084,7 @@ impl Backend {
|
||||||
&self.i18n
|
&self.i18n
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a request, process it, and return the encoded result.
|
pub fn run_command_bytes(
|
||||||
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(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
method: u32,
|
method: u32,
|
||||||
input: &[u8],
|
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 {
|
fn fire_progress_callback(&self, progress: Progress) -> bool {
|
||||||
if let Some(cb) = &self.progress_callback {
|
if let Some(cb) = &self.progress_callback {
|
||||||
let bytes = progress_to_proto_bytes(progress, &self.i18n);
|
let bytes = progress_to_proto_bytes(progress, &self.i18n);
|
||||||
|
@ -1105,126 +1155,9 @@ impl Backend {
|
||||||
ret
|
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> {
|
pub fn db_command(&self, input: &[u8]) -> Result<String> {
|
||||||
self.with_col(|col| db_command_bytes(&col.storage, input))
|
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> {
|
fn to_nids(ids: Vec<i64>) -> Vec<NoteID> {
|
||||||
|
|
|
@ -93,6 +93,18 @@ fn want_release_gil(method: u32) -> bool {
|
||||||
BackendMethod::CloseCollection => true,
|
BackendMethod::CloseCollection => true,
|
||||||
BackendMethod::AbortMediaSync => true,
|
BackendMethod::AbortMediaSync => true,
|
||||||
BackendMethod::BeforeUpload => 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 {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -101,23 +113,12 @@ fn want_release_gil(method: u32) -> bool {
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl Backend {
|
impl Backend {
|
||||||
fn command(&mut self, py: Python, input: &PyBytes, release_gil: bool) -> PyObject {
|
fn command(&mut self, py: Python, method: u32, input: &PyBytes) -> PyResult<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> {
|
|
||||||
let in_bytes = input.as_bytes();
|
let in_bytes = input.as_bytes();
|
||||||
if want_release_gil(method) {
|
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 {
|
} else {
|
||||||
self.backend.run_command_bytes2(method, in_bytes)
|
self.backend.run_command_bytes(method, in_bytes)
|
||||||
}
|
}
|
||||||
.map(|out_bytes| {
|
.map(|out_bytes| {
|
||||||
let out_obj = PyBytes::new(py, &out_bytes);
|
let out_obj = PyBytes::new(py, &out_bytes);
|
||||||
|
|
Loading…
Reference in a new issue