diff --git a/proto/backend.proto b/proto/backend.proto index 2b63fdf2e..e1a124bd0 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -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; diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 944df96f0..ea54a6091 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -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) diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 9a481fd70..0441f4483 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -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( diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py index 23013073d..ba6aaff21 100644 --- a/pylib/anki/latex.py +++ b/pylib/anki/latex.py @@ -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 diff --git a/pylib/anki/media.py b/pylib/anki/media.py index ff5f7ecd7..da6fcc934 100644 --- a/pylib/anki/media.py +++ b/pylib/anki/media.py @@ -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 diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index abdb6532a..cf3c28b1c 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -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@@ diff --git a/pylib/anki/template.py b/pylib/anki/template.py index bbae9a35b..6c4aafbf2 100644 --- a/pylib/anki/template.py +++ b/pylib/anki/template.py @@ -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"], ) diff --git a/pylib/tools/genbackend.py b/pylib/tools/genbackend.py index 8dbdd5702..2224dc388 100755 --- a/pylib/tools/genbackend.py +++ b/pylib/tools/genbackend.py @@ -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 diff --git a/qt/aqt/mediacheck.py b/qt/aqt/mediacheck.py index 2d1fdc1d5..4de188e9e 100644 --- a/qt/aqt/mediacheck.py +++ b/qt/aqt/mediacheck.py @@ -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 diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 4b3293d05..9273d1320 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -175,6 +175,141 @@ impl BackendService for Backend { .map(Into::into) }) } + + fn sched_timing_today(&mut self, _input: pb::Empty) -> Result { + self.with_col(|col| col.timing_today().map(Into::into)) + } + + fn deck_tree(&mut self, input: pb::DeckTreeIn) -> Result { + 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 { + 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 { + 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 { + 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 { + Ok(pb::Int32 { + val: local_minutes_west_for_stamp(input.val), + }) + } + + fn strip_av_tags(&mut self, input: pb::String) -> BackendResult { + Ok(pb::String { + val: strip_av_tags(&input.val).into(), + }) + } + + fn extract_av_tags( + &mut self, + input: pb::ExtractAvTagsIn, + ) -> BackendResult { + 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 { + 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 { 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 { - self.with_col(|col| col.timing_today().map(Into::into)) - } - - fn deck_tree(&self, input: pb::DeckTreeIn) -> Result { - 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 { 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 { - 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 { - 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 { - 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 { let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?; Ok(pb::GetCardOut {