mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
migrate more methods to service
This commit is contained in:
parent
9c20d9a02b
commit
175afa9fee
10 changed files with 296 additions and 292 deletions
|
@ -4,6 +4,9 @@ import "fluent.proto";
|
|||
|
||||
package backend_proto;
|
||||
|
||||
// Generic containers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
message Empty {}
|
||||
|
||||
message OptionalInt32 {
|
||||
|
@ -14,9 +17,33 @@ message OptionalUInt32 {
|
|||
uint32 val = 1;
|
||||
}
|
||||
|
||||
message Int32 {
|
||||
sint32 val = 1;
|
||||
}
|
||||
|
||||
message Int64 {
|
||||
int64 val = 1;
|
||||
}
|
||||
|
||||
message String {
|
||||
string val = 1;
|
||||
}
|
||||
|
||||
// New style RPC definitions
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
service BackendService {
|
||||
rpc RenderExistingCard (RenderExistingCardIn) returns (RenderCardOut);
|
||||
rpc RenderUncommittedCard (RenderUncommittedCardIn) returns (RenderCardOut);
|
||||
rpc SchedTimingToday (Empty) returns (SchedTimingTodayOut);
|
||||
rpc DeckTree (DeckTreeIn) returns (DeckTreeNode);
|
||||
rpc SearchCards (SearchCardsIn) returns (SearchCardsOut);
|
||||
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
|
||||
rpc CheckMedia (Empty) returns (CheckMediaOut);
|
||||
rpc LocalMinutesWest (Int64) returns (Int32);
|
||||
rpc StripAvTags (String) returns (String);
|
||||
rpc ExtractAVTags (ExtractAVTagsIn) returns (ExtractAVTagsOut);
|
||||
rpc ExtractLatex (ExtractLatexIn) returns (ExtractLatexOut);
|
||||
}
|
||||
|
||||
// Protobuf stored in .anki2 files
|
||||
|
@ -257,20 +284,13 @@ message I18nBackendInit {
|
|||
string locale_folder_path = 5;
|
||||
}
|
||||
|
||||
// Legacy enum - needs migrating to RPC above
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
message BackendInput {
|
||||
oneof value {
|
||||
Empty sched_timing_today = 17;
|
||||
DeckTreeIn deck_tree = 18;
|
||||
SearchCardsIn search_cards = 19;
|
||||
SearchNotesIn search_notes = 20;
|
||||
RenderUncommittedCardIn render_uncommitted_card = 21;
|
||||
int64 local_minutes_west = 22;
|
||||
string strip_av_tags = 23;
|
||||
ExtractAVTagsIn extract_av_tags = 24;
|
||||
ExtractLatexIn extract_latex = 25;
|
||||
AddMediaFileIn add_media_file = 26;
|
||||
SyncMediaIn sync_media = 27;
|
||||
Empty check_media = 28;
|
||||
TrashMediaFilesIn trash_media_files = 29;
|
||||
TranslateStringIn translate_string = 30;
|
||||
FormatTimeSpanIn format_time_span = 31;
|
||||
|
@ -327,7 +347,6 @@ message BackendInput {
|
|||
int32 set_local_minutes_west = 83;
|
||||
Empty get_preferences = 84;
|
||||
Preferences set_preferences = 85;
|
||||
RenderExistingCardIn render_existing_card = 86;
|
||||
Note cloze_numbers_in_note = 87;
|
||||
}
|
||||
}
|
||||
|
@ -335,10 +354,6 @@ message BackendInput {
|
|||
message BackendOutput {
|
||||
oneof value {
|
||||
// infallible commands
|
||||
sint32 local_minutes_west = 22;
|
||||
string strip_av_tags = 23;
|
||||
ExtractAVTagsOut extract_av_tags = 24;
|
||||
ExtractLatexOut extract_latex = 25;
|
||||
string translate_string = 30;
|
||||
string format_time_span = 31;
|
||||
string studied_today = 32;
|
||||
|
@ -347,14 +362,8 @@ message BackendOutput {
|
|||
ClozeNumbersInNoteOut cloze_numbers_in_note = 87;
|
||||
|
||||
// fallible commands
|
||||
SchedTimingTodayOut sched_timing_today = 17;
|
||||
DeckTreeNode deck_tree = 18;
|
||||
SearchCardsOut search_cards = 19;
|
||||
SearchNotesOut search_notes = 20;
|
||||
RenderCardOut render_uncommitted_card = 21;
|
||||
string add_media_file = 26;
|
||||
Empty sync_media = 27;
|
||||
MediaCheckOut check_media = 28;
|
||||
Empty trash_media_files = 29;
|
||||
Empty empty_trash = 34;
|
||||
Empty restore_trash = 35;
|
||||
|
@ -405,7 +414,6 @@ message BackendOutput {
|
|||
Empty set_local_minutes_west = 83;
|
||||
Preferences get_preferences = 84;
|
||||
Empty set_preferences = 85;
|
||||
RenderCardOut render_existing_card = 86;
|
||||
bytes get_stock_notetype_legacy = 60;
|
||||
|
||||
BackendError error = 2047;
|
||||
|
@ -580,7 +588,7 @@ message SyncMediaIn {
|
|||
string endpoint = 2;
|
||||
}
|
||||
|
||||
message MediaCheckOut {
|
||||
message CheckMediaOut {
|
||||
repeated string unused = 1;
|
||||
repeated string missing = 2;
|
||||
string report = 3;
|
||||
|
|
|
@ -26,7 +26,7 @@ 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
|
||||
from anki.rsbackend import TR, DBError, RustBackend, pb
|
||||
from anki.sched import Scheduler as V1Scheduler
|
||||
from anki.schedv2 import Scheduler as V2Scheduler
|
||||
from anki.tags import TagManager
|
||||
|
@ -393,7 +393,21 @@ select id from notes where id in %s and id not in (select nid from cards)"""
|
|||
def find_cards(
|
||||
self, query: str, order: Union[bool, str, int] = False, reverse: bool = False,
|
||||
) -> Sequence[int]:
|
||||
return self.backend.search_cards(query, order, reverse)
|
||||
if isinstance(order, str):
|
||||
mode = pb.SortOrder(custom=order)
|
||||
elif order is True:
|
||||
mode = pb.SortOrder(from_config=pb.Empty())
|
||||
elif order is False:
|
||||
mode = pb.SortOrder(none=pb.Empty())
|
||||
else:
|
||||
# sadly we can't use the protobuf type in a Union, so we
|
||||
# have to accept an int and convert it
|
||||
BKind = pb.BuiltinSearchOrder.BuiltinSortKind # pylint: disable=no-member
|
||||
kind = BKind.Value(BKind.Name(order))
|
||||
mode = pb.SortOrder(
|
||||
builtin=pb.BuiltinSearchOrder(kind=kind, reverse=reverse)
|
||||
)
|
||||
return self.backend.search_cards(query, mode)
|
||||
|
||||
def find_notes(self, query: str) -> Sequence[int]:
|
||||
return self.backend.search_notes(query)
|
||||
|
|
|
@ -128,7 +128,7 @@ class DeckManager:
|
|||
return self.col.backend.new_deck_legacy(filtered)
|
||||
|
||||
def deck_tree(self) -> pb.DeckTreeNode:
|
||||
return self.col.backend.deck_tree(include_counts=False)
|
||||
return self.col.backend.deck_tree(include_counts=False, top_deck_id=0)
|
||||
|
||||
@classmethod
|
||||
def find_deck_in_tree(
|
||||
|
|
|
@ -6,13 +6,14 @@ from __future__ import annotations
|
|||
import html
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import anki
|
||||
from anki import hooks
|
||||
from anki.lang import _
|
||||
from anki.models import NoteType
|
||||
from anki.rsbackend import ExtractedLatex
|
||||
from anki.rsbackend import pb
|
||||
from anki.template import TemplateRenderContext, TemplateRenderOutput
|
||||
from anki.utils import call, isMac, namedtmp, tmpdir
|
||||
|
||||
|
@ -33,6 +34,28 @@ if isMac:
|
|||
os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractedLatex:
|
||||
filename: str
|
||||
latex_body: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractedLatexOutput:
|
||||
html: str
|
||||
latex: List[ExtractedLatex]
|
||||
|
||||
@staticmethod
|
||||
def from_proto(proto: pb.ExtractLatexOut) -> ExtractedLatexOutput:
|
||||
return ExtractedLatexOutput(
|
||||
html=proto.text,
|
||||
latex=[
|
||||
ExtractedLatex(filename=l.filename, latex_body=l.latex_body)
|
||||
for l in proto.latex
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def on_card_did_render(
|
||||
output: TemplateRenderOutput, ctx: TemplateRenderContext
|
||||
) -> None:
|
||||
|
@ -63,7 +86,8 @@ def render_latex_returning_errors(
|
|||
header = model["latexPre"]
|
||||
footer = model["latexPost"]
|
||||
|
||||
out = col.backend.extract_latex(html, svg, expand_clozes)
|
||||
proto = col.backend.extract_latex(html, svg, expand_clozes)
|
||||
out = ExtractedLatexOutput.from_proto(proto)
|
||||
errors = []
|
||||
html = out.html
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ from typing import Any, Callable, List, Optional, Tuple
|
|||
import anki
|
||||
from anki.consts import *
|
||||
from anki.latex import render_latex, render_latex_returning_errors
|
||||
from anki.rsbackend import MediaCheckOutput
|
||||
from anki.rsbackend import pb
|
||||
from anki.utils import intTime
|
||||
|
||||
|
||||
|
@ -170,7 +170,7 @@ class MediaManager:
|
|||
# Checking media
|
||||
##########################################################################
|
||||
|
||||
def check(self) -> MediaCheckOutput:
|
||||
def check(self) -> pb.CheckMediaOut:
|
||||
output = self.col.backend.check_media()
|
||||
# files may have been renamed on disk, so an undo at this point could
|
||||
# break file references
|
||||
|
|
|
@ -151,39 +151,11 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
|||
return StringError(err.localized)
|
||||
|
||||
|
||||
def av_tag_to_native(tag: pb.AVTag) -> AVTag:
|
||||
val = tag.WhichOneof("value")
|
||||
if val == "sound_or_video":
|
||||
return SoundOrVideoTag(filename=tag.sound_or_video)
|
||||
else:
|
||||
return TTSTag(
|
||||
field_text=tag.tts.field_text,
|
||||
lang=tag.tts.lang,
|
||||
voices=list(tag.tts.voices),
|
||||
other_args=list(tag.tts.other_args),
|
||||
speed=tag.tts.speed,
|
||||
)
|
||||
|
||||
|
||||
MediaSyncProgress = pb.MediaSyncProgress
|
||||
|
||||
MediaCheckOutput = pb.MediaCheckOut
|
||||
|
||||
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractedLatex:
|
||||
filename: str
|
||||
latex_body: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractedLatexOutput:
|
||||
html: str
|
||||
latex: List[ExtractedLatex]
|
||||
|
||||
|
||||
class ProgressKind(enum.Enum):
|
||||
MediaSync = 0
|
||||
MediaCheck = 1
|
||||
|
@ -267,52 +239,6 @@ class RustBackend:
|
|||
release_gil=True,
|
||||
)
|
||||
|
||||
def sched_timing_today(self,) -> SchedTimingToday:
|
||||
return self._run_command(
|
||||
pb.BackendInput(sched_timing_today=pb.Empty())
|
||||
).sched_timing_today
|
||||
|
||||
def local_minutes_west(self, stamp: int) -> int:
|
||||
return self._run_command(
|
||||
pb.BackendInput(local_minutes_west=stamp)
|
||||
).local_minutes_west
|
||||
|
||||
def strip_av_tags(self, text: str) -> str:
|
||||
return self._run_command(pb.BackendInput(strip_av_tags=text)).strip_av_tags
|
||||
|
||||
def extract_av_tags(
|
||||
self, text: str, question_side: bool
|
||||
) -> Tuple[str, List[AVTag]]:
|
||||
out = self._run_command(
|
||||
pb.BackendInput(
|
||||
extract_av_tags=pb.ExtractAVTagsIn(
|
||||
text=text, question_side=question_side
|
||||
)
|
||||
)
|
||||
).extract_av_tags
|
||||
native_tags = list(map(av_tag_to_native, out.av_tags))
|
||||
|
||||
return out.text, native_tags
|
||||
|
||||
def extract_latex(
|
||||
self, text: str, svg: bool, expand_clozes: bool
|
||||
) -> ExtractedLatexOutput:
|
||||
out = self._run_command(
|
||||
pb.BackendInput(
|
||||
extract_latex=pb.ExtractLatexIn(
|
||||
text=text, svg=svg, expand_clozes=expand_clozes
|
||||
)
|
||||
)
|
||||
).extract_latex
|
||||
|
||||
return ExtractedLatexOutput(
|
||||
html=out.text,
|
||||
latex=[
|
||||
ExtractedLatex(filename=l.filename, latex_body=l.latex_body)
|
||||
for l in out.latex
|
||||
],
|
||||
)
|
||||
|
||||
def add_file_to_media_folder(self, desired_name: str, data: bytes) -> str:
|
||||
return self._run_command(
|
||||
pb.BackendInput(
|
||||
|
@ -326,11 +252,6 @@ class RustBackend:
|
|||
release_gil=True,
|
||||
)
|
||||
|
||||
def check_media(self) -> MediaCheckOutput:
|
||||
return self._run_command(
|
||||
pb.BackendInput(check_media=pb.Empty()), release_gil=True,
|
||||
).check_media
|
||||
|
||||
def trash_media_files(self, fnames: List[str]) -> None:
|
||||
self._run_command(
|
||||
pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames))
|
||||
|
@ -396,32 +317,6 @@ class RustBackend:
|
|||
def _db_command(self, input: Dict[str, Any]) -> Any:
|
||||
return orjson.loads(self._backend.db_command(orjson.dumps(input)))
|
||||
|
||||
def search_cards(
|
||||
self, search: str, order: Union[bool, str, int], reverse: bool = False
|
||||
) -> Sequence[int]:
|
||||
if isinstance(order, str):
|
||||
mode = pb.SortOrder(custom=order)
|
||||
elif order is True:
|
||||
mode = pb.SortOrder(from_config=pb.Empty())
|
||||
elif order is False:
|
||||
mode = pb.SortOrder(none=pb.Empty())
|
||||
else:
|
||||
# sadly we can't use the protobuf type in a Union, so we
|
||||
# have to accept an int and convert it
|
||||
kind = BuiltinSortKind.Value(BuiltinSortKind.Name(order))
|
||||
mode = pb.SortOrder(
|
||||
builtin=pb.BuiltinSearchOrder(kind=kind, reverse=reverse)
|
||||
)
|
||||
|
||||
return self._run_command(
|
||||
pb.BackendInput(search_cards=pb.SearchCardsIn(search=search, order=mode))
|
||||
).search_cards.card_ids
|
||||
|
||||
def search_notes(self, search: str) -> Sequence[int]:
|
||||
return self._run_command(
|
||||
pb.BackendInput(search_notes=pb.SearchNotesIn(search=search))
|
||||
).search_notes.note_ids
|
||||
|
||||
def get_card(self, cid: int) -> Optional[pb.Card]:
|
||||
output = self._run_command(pb.BackendInput(get_card=cid)).get_card
|
||||
if output.HasField("card"):
|
||||
|
@ -667,15 +562,6 @@ class RustBackend:
|
|||
def remove_deck(self, did: int) -> None:
|
||||
self._run_command(pb.BackendInput(remove_deck=did))
|
||||
|
||||
def deck_tree(self, include_counts: bool, top_deck_id: int = 0) -> DeckTreeNode:
|
||||
return self._run_command(
|
||||
pb.BackendInput(
|
||||
deck_tree=pb.DeckTreeIn(
|
||||
include_counts=include_counts, top_deck_id=top_deck_id
|
||||
)
|
||||
)
|
||||
).deck_tree
|
||||
|
||||
def check_database(self) -> List[str]:
|
||||
return list(
|
||||
self._run_command(
|
||||
|
@ -797,6 +683,62 @@ class RustBackend:
|
|||
output.ParseFromString(self._run_command2(2, input))
|
||||
return output
|
||||
|
||||
def sched_timing_today(self) -> pb.SchedTimingTodayOut:
|
||||
input = pb.Empty()
|
||||
output = pb.SchedTimingTodayOut()
|
||||
output.ParseFromString(self._run_command2(3, input))
|
||||
return output
|
||||
|
||||
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(4, input))
|
||||
return output
|
||||
|
||||
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(5, 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(6, input))
|
||||
return output.note_ids
|
||||
|
||||
def check_media(self) -> pb.CheckMediaOut:
|
||||
input = pb.Empty()
|
||||
output = pb.CheckMediaOut()
|
||||
output.ParseFromString(self._run_command2(7, input))
|
||||
return output
|
||||
|
||||
def local_minutes_west(self, val: int) -> int:
|
||||
input = pb.Int64(val=val)
|
||||
output = pb.Int32()
|
||||
output.ParseFromString(self._run_command2(8, input))
|
||||
return output.val
|
||||
|
||||
def strip_av_tags(self, val: str) -> str:
|
||||
input = pb.String(val=val)
|
||||
output = pb.String()
|
||||
output.ParseFromString(self._run_command2(9, input))
|
||||
return output.val
|
||||
|
||||
def extract_a_v_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(10, input))
|
||||
return output
|
||||
|
||||
def extract_latex(
|
||||
self, text: str, svg: bool, expand_clozes: bool
|
||||
) -> pb.ExtractLatexOut:
|
||||
input = pb.ExtractLatexIn(text=text, svg=svg, expand_clozes=expand_clozes)
|
||||
output = pb.ExtractLatexOut()
|
||||
output.ParseFromString(self._run_command2(11, input))
|
||||
return output
|
||||
|
||||
# @@AUTOGEN@@
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ from anki.decks import DeckManager
|
|||
from anki.models import NoteType
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import pb, to_json_bytes
|
||||
from anki.sound import AVTag
|
||||
from anki.sound import AVTag, SoundOrVideoTag, TTSTag
|
||||
|
||||
CARD_BLANK_HELP = (
|
||||
"https://anki.tenderapp.com/kb/card-appearance/the-front-of-this-card-is-blank"
|
||||
|
@ -86,6 +86,24 @@ class PartiallyRenderedCard:
|
|||
return results
|
||||
|
||||
|
||||
def av_tag_to_native(tag: pb.AVTag) -> AVTag:
|
||||
val = tag.WhichOneof("value")
|
||||
if val == "sound_or_video":
|
||||
return SoundOrVideoTag(filename=tag.sound_or_video)
|
||||
else:
|
||||
return TTSTag(
|
||||
field_text=tag.tts.field_text,
|
||||
lang=tag.tts.lang,
|
||||
voices=list(tag.tts.voices),
|
||||
other_args=list(tag.tts.other_args),
|
||||
speed=tag.tts.speed,
|
||||
)
|
||||
|
||||
|
||||
def av_tags_to_native(tags: Sequence[pb.AVTag]) -> List[AVTag]:
|
||||
return list(map(av_tag_to_native, tags))
|
||||
|
||||
|
||||
class TemplateRenderContext:
|
||||
"""Holds information for the duration of one card render.
|
||||
|
||||
|
@ -197,16 +215,16 @@ class TemplateRenderContext:
|
|||
)
|
||||
|
||||
qtext = apply_custom_filters(partial.qnodes, self, front_side=None)
|
||||
qtext, q_avtags = self.col().backend.extract_av_tags(qtext, True)
|
||||
qout = self.col().backend.extract_a_v_tags(qtext, True)
|
||||
|
||||
atext = apply_custom_filters(partial.anodes, self, front_side=qtext)
|
||||
atext, a_avtags = self.col().backend.extract_av_tags(atext, False)
|
||||
aout = self.col().backend.extract_a_v_tags(atext, False)
|
||||
|
||||
output = TemplateRenderOutput(
|
||||
question_text=qtext,
|
||||
answer_text=atext,
|
||||
question_av_tags=q_avtags,
|
||||
answer_av_tags=a_avtags,
|
||||
question_text=qout.text,
|
||||
answer_text=aout.text,
|
||||
question_av_tags=av_tags_to_native(qout.av_tags),
|
||||
answer_av_tags=av_tags_to_native(aout.av_tags),
|
||||
css=self.note_type()["css"],
|
||||
)
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ LABEL_REPEATED = 3
|
|||
def python_type(field):
|
||||
type = python_type_inner(field)
|
||||
if field.label == LABEL_REPEATED:
|
||||
type = f"List[{type}]"
|
||||
type = f"Sequence[{type}]"
|
||||
return type
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from typing import Iterable, List, Optional, Sequence, TypeVar
|
|||
|
||||
import aqt
|
||||
from anki import hooks
|
||||
from anki.rsbackend import TR, Interrupted, MediaCheckOutput, Progress, ProgressKind
|
||||
from anki.rsbackend import TR, Interrupted, Progress, ProgressKind, pb
|
||||
from aqt.qt import *
|
||||
from aqt.utils import askUser, restoreGeom, saveGeom, showText, tooltip, tr
|
||||
|
||||
|
@ -52,7 +52,7 @@ class MediaChecker:
|
|||
self.mw.taskman.run_on_main(lambda: self.mw.progress.update(progress.val))
|
||||
return True
|
||||
|
||||
def _check(self) -> MediaCheckOutput:
|
||||
def _check(self) -> pb.CheckMediaOut:
|
||||
"Run the check on a background thread."
|
||||
return self.mw.col.media.check()
|
||||
|
||||
|
@ -65,7 +65,7 @@ class MediaChecker:
|
|||
if isinstance(exc, Interrupted):
|
||||
return
|
||||
|
||||
output: MediaCheckOutput = future.result()
|
||||
output: pb.CheckMediaOut = future.result()
|
||||
report = output.report
|
||||
|
||||
# show report and offer to delete
|
||||
|
|
|
@ -175,6 +175,141 @@ impl BackendService for Backend {
|
|||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn sched_timing_today(&mut self, _input: pb::Empty) -> Result<pb::SchedTimingTodayOut> {
|
||||
self.with_col(|col| col.timing_today().map(Into::into))
|
||||
}
|
||||
|
||||
fn deck_tree(&mut self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> {
|
||||
let lim = if input.top_deck_id > 0 {
|
||||
Some(DeckID(input.top_deck_id))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.with_col(|col| col.deck_tree(input.include_counts, lim))
|
||||
}
|
||||
|
||||
fn check_media(&mut self, _input: pb::Empty) -> Result<pb::CheckMediaOut> {
|
||||
let callback =
|
||||
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
|
||||
|
||||
self.with_col(|col| {
|
||||
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
|
||||
col.transact(None, |ctx| {
|
||||
let mut checker = MediaChecker::new(ctx, &mgr, callback);
|
||||
let mut output = checker.check()?;
|
||||
|
||||
let report = checker.summarize_output(&mut output);
|
||||
|
||||
Ok(pb::CheckMediaOut {
|
||||
unused: output.unused,
|
||||
missing: output.missing,
|
||||
report,
|
||||
have_trash: output.trash_count > 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn search_cards(&mut self, input: pb::SearchCardsIn) -> Result<pb::SearchCardsOut> {
|
||||
self.with_col(|col| {
|
||||
let order = if let Some(order) = input.order {
|
||||
use pb::sort_order::Value as V;
|
||||
match order.value {
|
||||
Some(V::None(_)) => SortMode::NoOrder,
|
||||
Some(V::Custom(s)) => SortMode::Custom(s),
|
||||
Some(V::FromConfig(_)) => SortMode::FromConfig,
|
||||
Some(V::Builtin(b)) => SortMode::Builtin {
|
||||
kind: sort_kind_from_pb(b.kind),
|
||||
reverse: b.reverse,
|
||||
},
|
||||
None => SortMode::FromConfig,
|
||||
}
|
||||
} else {
|
||||
SortMode::FromConfig
|
||||
};
|
||||
let cids = col.search_cards(&input.search, order)?;
|
||||
Ok(pb::SearchCardsOut {
|
||||
card_ids: cids.into_iter().map(|v| v.0).collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn search_notes(&mut self, input: pb::SearchNotesIn) -> Result<pb::SearchNotesOut> {
|
||||
self.with_col(|col| {
|
||||
let nids = col.search_notes(&input.search)?;
|
||||
Ok(pb::SearchNotesOut {
|
||||
note_ids: nids.into_iter().map(|v| v.0).collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn local_minutes_west(&mut self, input: pb::Int64) -> BackendResult<pb::Int32> {
|
||||
Ok(pb::Int32 {
|
||||
val: local_minutes_west_for_stamp(input.val),
|
||||
})
|
||||
}
|
||||
|
||||
fn strip_av_tags(&mut self, input: pb::String) -> BackendResult<pb::String> {
|
||||
Ok(pb::String {
|
||||
val: strip_av_tags(&input.val).into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_av_tags(
|
||||
&mut self,
|
||||
input: pb::ExtractAvTagsIn,
|
||||
) -> BackendResult<pb::ExtractAvTagsOut> {
|
||||
let (text, tags) = extract_av_tags(&input.text, input.question_side);
|
||||
let pt_tags = tags
|
||||
.into_iter()
|
||||
.map(|avtag| match avtag {
|
||||
AVTag::SoundOrVideo(file) => pb::AvTag {
|
||||
value: Some(pb::av_tag::Value::SoundOrVideo(file)),
|
||||
},
|
||||
AVTag::TextToSpeech {
|
||||
field_text,
|
||||
lang,
|
||||
voices,
|
||||
other_args,
|
||||
speed,
|
||||
} => pb::AvTag {
|
||||
value: Some(pb::av_tag::Value::Tts(pb::TtsTag {
|
||||
field_text,
|
||||
lang,
|
||||
voices,
|
||||
other_args,
|
||||
speed,
|
||||
})),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(pb::ExtractAvTagsOut {
|
||||
text: text.into(),
|
||||
av_tags: pt_tags,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_latex(&mut self, input: pb::ExtractLatexIn) -> BackendResult<pb::ExtractLatexOut> {
|
||||
let func = if input.expand_clozes {
|
||||
extract_latex_expanding_clozes
|
||||
} else {
|
||||
extract_latex
|
||||
};
|
||||
let (text, extracted) = func(&input.text, input.svg);
|
||||
|
||||
Ok(pb::ExtractLatexOut {
|
||||
text,
|
||||
latex: extracted
|
||||
.into_iter()
|
||||
.map(|e: ExtractedLatex| pb::ExtractedLatex {
|
||||
filename: e.fname,
|
||||
latex_body: e.latex,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
|
@ -267,20 +402,11 @@ impl Backend {
|
|||
) -> Result<pb::backend_output::Value> {
|
||||
use pb::backend_output::Value as OValue;
|
||||
Ok(match ival {
|
||||
Value::SchedTimingToday(_) => OValue::SchedTimingToday(self.sched_timing_today()?),
|
||||
Value::DeckTree(input) => OValue::DeckTree(self.deck_tree(input)?),
|
||||
Value::LocalMinutesWest(stamp) => {
|
||||
OValue::LocalMinutesWest(local_minutes_west_for_stamp(stamp))
|
||||
}
|
||||
Value::StripAvTags(text) => OValue::StripAvTags(strip_av_tags(&text).into()),
|
||||
Value::ExtractAvTags(input) => OValue::ExtractAvTags(self.extract_av_tags(input)),
|
||||
Value::ExtractLatex(input) => OValue::ExtractLatex(self.extract_latex(input)),
|
||||
Value::AddMediaFile(input) => OValue::AddMediaFile(self.add_media_file(input)?),
|
||||
Value::SyncMedia(input) => {
|
||||
self.sync_media(input)?;
|
||||
OValue::SyncMedia(Empty {})
|
||||
}
|
||||
Value::CheckMedia(_) => OValue::CheckMedia(self.check_media()?),
|
||||
Value::TrashMediaFiles(input) => {
|
||||
self.remove_media_files(&input.fnames)?;
|
||||
OValue::TrashMediaFiles(Empty {})
|
||||
|
@ -313,8 +439,6 @@ impl Backend {
|
|||
self.close_collection(input.downgrade_to_schema11)?;
|
||||
OValue::CloseCollection(Empty {})
|
||||
}
|
||||
Value::SearchCards(input) => OValue::SearchCards(self.search_cards(input)?),
|
||||
Value::SearchNotes(input) => OValue::SearchNotes(self.search_notes(input)?),
|
||||
Value::GetCard(cid) => OValue::GetCard(self.get_card(cid)?),
|
||||
Value::UpdateCard(card) => {
|
||||
self.update_card(card)?;
|
||||
|
@ -422,12 +546,6 @@ impl Backend {
|
|||
self.set_preferences(prefs)?;
|
||||
pb::Empty {}
|
||||
}),
|
||||
Value::RenderExistingCard(input) => {
|
||||
OValue::RenderExistingCard(self.render_existing_card(input)?)
|
||||
}
|
||||
Value::RenderUncommittedCard(input) => {
|
||||
OValue::RenderUncommittedCard(self.render_uncommitted_card(input)?)
|
||||
}
|
||||
Value::ClozeNumbersInNote(note) => {
|
||||
OValue::ClozeNumbersInNote(self.cloze_numbers_in_note(note))
|
||||
}
|
||||
|
@ -497,71 +615,6 @@ impl Backend {
|
|||
self.progress_callback = progress_cb;
|
||||
}
|
||||
|
||||
fn sched_timing_today(&self) -> Result<pb::SchedTimingTodayOut> {
|
||||
self.with_col(|col| col.timing_today().map(Into::into))
|
||||
}
|
||||
|
||||
fn deck_tree(&self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> {
|
||||
let lim = if input.top_deck_id > 0 {
|
||||
Some(DeckID(input.top_deck_id))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.with_col(|col| col.deck_tree(input.include_counts, lim))
|
||||
}
|
||||
|
||||
fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> pb::ExtractAvTagsOut {
|
||||
let (text, tags) = extract_av_tags(&input.text, input.question_side);
|
||||
let pt_tags = tags
|
||||
.into_iter()
|
||||
.map(|avtag| match avtag {
|
||||
AVTag::SoundOrVideo(file) => pb::AvTag {
|
||||
value: Some(pb::av_tag::Value::SoundOrVideo(file)),
|
||||
},
|
||||
AVTag::TextToSpeech {
|
||||
field_text,
|
||||
lang,
|
||||
voices,
|
||||
other_args,
|
||||
speed,
|
||||
} => pb::AvTag {
|
||||
value: Some(pb::av_tag::Value::Tts(pb::TtsTag {
|
||||
field_text,
|
||||
lang,
|
||||
voices,
|
||||
other_args,
|
||||
speed,
|
||||
})),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
pb::ExtractAvTagsOut {
|
||||
text: text.into(),
|
||||
av_tags: pt_tags,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_latex(&self, input: pb::ExtractLatexIn) -> pb::ExtractLatexOut {
|
||||
let func = if input.expand_clozes {
|
||||
extract_latex_expanding_clozes
|
||||
} else {
|
||||
extract_latex
|
||||
};
|
||||
let (text, extracted) = func(&input.text, input.svg);
|
||||
|
||||
pb::ExtractLatexOut {
|
||||
text,
|
||||
latex: extracted
|
||||
.into_iter()
|
||||
.map(|e: ExtractedLatex| pb::ExtractedLatex {
|
||||
filename: e.fname,
|
||||
latex_body: e.latex,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> Result<String> {
|
||||
self.with_col(|col| {
|
||||
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
|
||||
|
@ -626,28 +679,6 @@ impl Backend {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_media(&self) -> Result<pb::MediaCheckOut> {
|
||||
let callback =
|
||||
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
|
||||
|
||||
self.with_col(|col| {
|
||||
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
|
||||
col.transact(None, |ctx| {
|
||||
let mut checker = MediaChecker::new(ctx, &mgr, callback);
|
||||
let mut output = checker.check()?;
|
||||
|
||||
let report = checker.summarize_output(&mut output);
|
||||
|
||||
Ok(pb::MediaCheckOut {
|
||||
unused: output.unused,
|
||||
missing: output.missing,
|
||||
report,
|
||||
have_trash: output.trash_count > 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_media_files(&self, fnames: &[String]) -> Result<()> {
|
||||
self.with_col(|col| {
|
||||
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
|
||||
|
@ -720,39 +751,6 @@ impl Backend {
|
|||
self.with_col(|col| db_command_bytes(&col.storage, input))
|
||||
}
|
||||
|
||||
fn search_cards(&self, input: pb::SearchCardsIn) -> Result<pb::SearchCardsOut> {
|
||||
self.with_col(|col| {
|
||||
let order = if let Some(order) = input.order {
|
||||
use pb::sort_order::Value as V;
|
||||
match order.value {
|
||||
Some(V::None(_)) => SortMode::NoOrder,
|
||||
Some(V::Custom(s)) => SortMode::Custom(s),
|
||||
Some(V::FromConfig(_)) => SortMode::FromConfig,
|
||||
Some(V::Builtin(b)) => SortMode::Builtin {
|
||||
kind: sort_kind_from_pb(b.kind),
|
||||
reverse: b.reverse,
|
||||
},
|
||||
None => SortMode::FromConfig,
|
||||
}
|
||||
} else {
|
||||
SortMode::FromConfig
|
||||
};
|
||||
let cids = col.search_cards(&input.search, order)?;
|
||||
Ok(pb::SearchCardsOut {
|
||||
card_ids: cids.into_iter().map(|v| v.0).collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn search_notes(&self, input: pb::SearchNotesIn) -> Result<pb::SearchNotesOut> {
|
||||
self.with_col(|col| {
|
||||
let nids = col.search_notes(&input.search)?;
|
||||
Ok(pb::SearchNotesOut {
|
||||
note_ids: nids.into_iter().map(|v| v.0).collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_card(&self, cid: i64) -> Result<pb::GetCardOut> {
|
||||
let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?;
|
||||
Ok(pb::GetCardOut {
|
||||
|
|
Loading…
Reference in a new issue