migrate more methods to service

This commit is contained in:
Damien Elmes 2020-05-22 21:25:25 +10:00
parent 9c20d9a02b
commit 175afa9fee
10 changed files with 296 additions and 292 deletions

View file

@ -4,6 +4,9 @@ import "fluent.proto";
package backend_proto; package backend_proto;
// Generic containers
///////////////////////////////////////////////////////////
message Empty {} message Empty {}
message OptionalInt32 { message OptionalInt32 {
@ -14,9 +17,33 @@ message OptionalUInt32 {
uint32 val = 1; uint32 val = 1;
} }
message Int32 {
sint32 val = 1;
}
message Int64 {
int64 val = 1;
}
message String {
string val = 1;
}
// New style RPC definitions
///////////////////////////////////////////////////////////
service BackendService { service BackendService {
rpc RenderExistingCard (RenderExistingCardIn) returns (RenderCardOut); rpc RenderExistingCard (RenderExistingCardIn) returns (RenderCardOut);
rpc RenderUncommittedCard (RenderUncommittedCardIn) 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 // Protobuf stored in .anki2 files
@ -257,20 +284,13 @@ message I18nBackendInit {
string locale_folder_path = 5; string locale_folder_path = 5;
} }
// Legacy enum - needs migrating to RPC above
///////////////////////////////////////////////////////////
message BackendInput { message BackendInput {
oneof value { 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; AddMediaFileIn add_media_file = 26;
SyncMediaIn sync_media = 27; SyncMediaIn sync_media = 27;
Empty check_media = 28;
TrashMediaFilesIn trash_media_files = 29; TrashMediaFilesIn trash_media_files = 29;
TranslateStringIn translate_string = 30; TranslateStringIn translate_string = 30;
FormatTimeSpanIn format_time_span = 31; FormatTimeSpanIn format_time_span = 31;
@ -327,7 +347,6 @@ message BackendInput {
int32 set_local_minutes_west = 83; int32 set_local_minutes_west = 83;
Empty get_preferences = 84; Empty get_preferences = 84;
Preferences set_preferences = 85; Preferences set_preferences = 85;
RenderExistingCardIn render_existing_card = 86;
Note cloze_numbers_in_note = 87; Note cloze_numbers_in_note = 87;
} }
} }
@ -335,10 +354,6 @@ message BackendInput {
message BackendOutput { message BackendOutput {
oneof value { oneof value {
// infallible commands // 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 translate_string = 30;
string format_time_span = 31; string format_time_span = 31;
string studied_today = 32; string studied_today = 32;
@ -347,14 +362,8 @@ message BackendOutput {
ClozeNumbersInNoteOut cloze_numbers_in_note = 87; ClozeNumbersInNoteOut cloze_numbers_in_note = 87;
// fallible commands // 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; string add_media_file = 26;
Empty sync_media = 27; Empty sync_media = 27;
MediaCheckOut check_media = 28;
Empty trash_media_files = 29; Empty trash_media_files = 29;
Empty empty_trash = 34; Empty empty_trash = 34;
Empty restore_trash = 35; Empty restore_trash = 35;
@ -405,7 +414,6 @@ message BackendOutput {
Empty set_local_minutes_west = 83; Empty set_local_minutes_west = 83;
Preferences get_preferences = 84; Preferences get_preferences = 84;
Empty set_preferences = 85; Empty set_preferences = 85;
RenderCardOut render_existing_card = 86;
bytes get_stock_notetype_legacy = 60; bytes get_stock_notetype_legacy = 60;
BackendError error = 2047; BackendError error = 2047;
@ -580,7 +588,7 @@ message SyncMediaIn {
string endpoint = 2; string endpoint = 2;
} }
message MediaCheckOut { message CheckMediaOut {
repeated string unused = 1; repeated string unused = 1;
repeated string missing = 2; repeated string missing = 2;
string report = 3; string report = 3;

View file

@ -26,7 +26,7 @@ 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 from anki.rsbackend import TR, DBError, 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
@ -393,7 +393,21 @@ select id from notes where id in %s and id not in (select nid from cards)"""
def find_cards( def find_cards(
self, query: str, order: Union[bool, str, int] = False, reverse: bool = False, self, query: str, order: Union[bool, str, int] = False, reverse: bool = False,
) -> Sequence[int]: ) -> 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]: def find_notes(self, query: str) -> Sequence[int]:
return self.backend.search_notes(query) return self.backend.search_notes(query)

View file

@ -128,7 +128,7 @@ class DeckManager:
return self.col.backend.new_deck_legacy(filtered) return self.col.backend.new_deck_legacy(filtered)
def deck_tree(self) -> pb.DeckTreeNode: 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 @classmethod
def find_deck_in_tree( def find_deck_in_tree(

View file

@ -6,13 +6,14 @@ from __future__ import annotations
import html import html
import os import os
import re import re
from dataclasses import dataclass
from typing import Any, List, Optional, Tuple from typing import Any, List, Optional, Tuple
import anki import anki
from anki import hooks from anki import hooks
from anki.lang import _ from anki.lang import _
from anki.models import NoteType from anki.models import NoteType
from anki.rsbackend import ExtractedLatex from anki.rsbackend import pb
from anki.template import TemplateRenderContext, TemplateRenderOutput from anki.template import TemplateRenderContext, TemplateRenderOutput
from anki.utils import call, isMac, namedtmp, tmpdir from anki.utils import call, isMac, namedtmp, tmpdir
@ -33,6 +34,28 @@ if isMac:
os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin" 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( def on_card_did_render(
output: TemplateRenderOutput, ctx: TemplateRenderContext output: TemplateRenderOutput, ctx: TemplateRenderContext
) -> None: ) -> None:
@ -63,7 +86,8 @@ def render_latex_returning_errors(
header = model["latexPre"] header = model["latexPre"]
footer = model["latexPost"] 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 = [] errors = []
html = out.html html = out.html

View file

@ -15,7 +15,7 @@ from typing import Any, Callable, List, Optional, Tuple
import anki import anki
from anki.consts import * from anki.consts import *
from anki.latex import render_latex, render_latex_returning_errors 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 from anki.utils import intTime
@ -170,7 +170,7 @@ class MediaManager:
# Checking media # Checking media
########################################################################## ##########################################################################
def check(self) -> MediaCheckOutput: def check(self) -> pb.CheckMediaOut:
output = self.col.backend.check_media() output = self.col.backend.check_media()
# files may have been renamed on disk, so an undo at this point could # files may have been renamed on disk, so an undo at this point could
# break file references # break file references

View file

@ -151,39 +151,11 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
return StringError(err.localized) 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 MediaSyncProgress = pb.MediaSyncProgress
MediaCheckOutput = pb.MediaCheckOut
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
@dataclass
class ExtractedLatex:
filename: str
latex_body: str
@dataclass
class ExtractedLatexOutput:
html: str
latex: List[ExtractedLatex]
class ProgressKind(enum.Enum): class ProgressKind(enum.Enum):
MediaSync = 0 MediaSync = 0
MediaCheck = 1 MediaCheck = 1
@ -267,52 +239,6 @@ class RustBackend:
release_gil=True, 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: def add_file_to_media_folder(self, desired_name: str, data: bytes) -> str:
return self._run_command( return self._run_command(
pb.BackendInput( pb.BackendInput(
@ -326,11 +252,6 @@ class RustBackend:
release_gil=True, 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: def trash_media_files(self, fnames: List[str]) -> None:
self._run_command( self._run_command(
pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames)) pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames))
@ -396,32 +317,6 @@ 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 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]: def get_card(self, cid: int) -> Optional[pb.Card]:
output = self._run_command(pb.BackendInput(get_card=cid)).get_card output = self._run_command(pb.BackendInput(get_card=cid)).get_card
if output.HasField("card"): if output.HasField("card"):
@ -667,15 +562,6 @@ class RustBackend:
def remove_deck(self, did: int) -> None: def remove_deck(self, did: int) -> None:
self._run_command(pb.BackendInput(remove_deck=did)) 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]: def check_database(self) -> List[str]:
return list( return list(
self._run_command( self._run_command(
@ -797,6 +683,62 @@ class RustBackend:
output.ParseFromString(self._run_command2(2, input)) output.ParseFromString(self._run_command2(2, input))
return output 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@@ # @@AUTOGEN@@

View file

@ -38,7 +38,7 @@ from anki.decks import DeckManager
from anki.models import NoteType from anki.models import NoteType
from anki.notes import Note from anki.notes import Note
from anki.rsbackend import pb, to_json_bytes from anki.rsbackend import pb, to_json_bytes
from anki.sound import AVTag from anki.sound import AVTag, SoundOrVideoTag, TTSTag
CARD_BLANK_HELP = ( CARD_BLANK_HELP = (
"https://anki.tenderapp.com/kb/card-appearance/the-front-of-this-card-is-blank" "https://anki.tenderapp.com/kb/card-appearance/the-front-of-this-card-is-blank"
@ -86,6 +86,24 @@ class PartiallyRenderedCard:
return results 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: class TemplateRenderContext:
"""Holds information for the duration of one card render. """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 = 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 = 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( output = TemplateRenderOutput(
question_text=qtext, question_text=qout.text,
answer_text=atext, answer_text=aout.text,
question_av_tags=q_avtags, question_av_tags=av_tags_to_native(qout.av_tags),
answer_av_tags=a_avtags, answer_av_tags=av_tags_to_native(aout.av_tags),
css=self.note_type()["css"], css=self.note_type()["css"],
) )

View file

@ -33,7 +33,7 @@ LABEL_REPEATED = 3
def python_type(field): def python_type(field):
type = python_type_inner(field) type = python_type_inner(field)
if field.label == LABEL_REPEATED: if field.label == LABEL_REPEATED:
type = f"List[{type}]" type = f"Sequence[{type}]"
return type return type

View file

@ -10,7 +10,7 @@ from typing import Iterable, List, Optional, Sequence, TypeVar
import aqt import aqt
from anki import hooks 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.qt import *
from aqt.utils import askUser, restoreGeom, saveGeom, showText, tooltip, tr 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)) self.mw.taskman.run_on_main(lambda: self.mw.progress.update(progress.val))
return True return True
def _check(self) -> MediaCheckOutput: def _check(self) -> pb.CheckMediaOut:
"Run the check on a background thread." "Run the check on a background thread."
return self.mw.col.media.check() return self.mw.col.media.check()
@ -65,7 +65,7 @@ class MediaChecker:
if isinstance(exc, Interrupted): if isinstance(exc, Interrupted):
return return
output: MediaCheckOutput = future.result() output: pb.CheckMediaOut = future.result()
report = output.report report = output.report
# show report and offer to delete # show report and offer to delete

View file

@ -175,6 +175,141 @@ impl BackendService for Backend {
.map(Into::into) .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 { impl Backend {
@ -267,20 +402,11 @@ impl Backend {
) -> Result<pb::backend_output::Value> { ) -> Result<pb::backend_output::Value> {
use pb::backend_output::Value as OValue; use pb::backend_output::Value as OValue;
Ok(match ival { 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::AddMediaFile(input) => OValue::AddMediaFile(self.add_media_file(input)?),
Value::SyncMedia(input) => { Value::SyncMedia(input) => {
self.sync_media(input)?; self.sync_media(input)?;
OValue::SyncMedia(Empty {}) OValue::SyncMedia(Empty {})
} }
Value::CheckMedia(_) => OValue::CheckMedia(self.check_media()?),
Value::TrashMediaFiles(input) => { Value::TrashMediaFiles(input) => {
self.remove_media_files(&input.fnames)?; self.remove_media_files(&input.fnames)?;
OValue::TrashMediaFiles(Empty {}) OValue::TrashMediaFiles(Empty {})
@ -313,8 +439,6 @@ impl Backend {
self.close_collection(input.downgrade_to_schema11)?; self.close_collection(input.downgrade_to_schema11)?;
OValue::CloseCollection(Empty {}) 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::GetCard(cid) => OValue::GetCard(self.get_card(cid)?),
Value::UpdateCard(card) => { Value::UpdateCard(card) => {
self.update_card(card)?; self.update_card(card)?;
@ -422,12 +546,6 @@ impl Backend {
self.set_preferences(prefs)?; self.set_preferences(prefs)?;
pb::Empty {} 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) => { Value::ClozeNumbersInNote(note) => {
OValue::ClozeNumbersInNote(self.cloze_numbers_in_note(note)) OValue::ClozeNumbersInNote(self.cloze_numbers_in_note(note))
} }
@ -497,71 +615,6 @@ impl Backend {
self.progress_callback = progress_cb; 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> { fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> Result<String> {
self.with_col(|col| { self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; 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<()> { fn remove_media_files(&self, fnames: &[String]) -> Result<()> {
self.with_col(|col| { self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?; 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)) 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> { fn get_card(&self, cid: i64) -> Result<pb::GetCardOut> {
let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?; let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?;
Ok(pb::GetCardOut { Ok(pb::GetCardOut {