mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -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;
|
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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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@@
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue