rework backend codegen to support multiple services; split out sched

Rust requires all methods of impl Trait to be in a single file, which
means we had a giant backend/mod.rs covering all exposed methods. By
using separate service definitions for the separate areas, and updating
the code generation, we can split it into more manageable chunks -
this commit starts with the scheduling code.

In the long run, we'll probably want to split up the protobuf file into
multiple files as well.

Also dropped want_release_gil() from rsbridge, and the associated method
enum. While it allows us to skip the thread save/restore and mutex unlock/
lock, it looks to only be buying about 2.5% extra performance in the
best case (tested with timeit+format_timespan), and the majority of
the backend methods deal with I/O, and thus were already releasing the
GIL.
This commit is contained in:
Damien Elmes 2021-03-11 14:33:57 +10:00
parent 060293c8f5
commit 5df684fa6b
9 changed files with 366 additions and 381 deletions

View File

@ -36,7 +36,7 @@ py_binary(
genrule(
name = "rsbackend_gen",
outs = ["generated.py"],
cmd = "$(location genbackend) > $@",
cmd = "$(location genbackend) $@",
tools = ["genbackend"],
)

View File

@ -95,10 +95,10 @@ class RustBackend(RustBackendGenerated):
)
return self.format_timespan(seconds=seconds, context=context)
def _run_command(self, method: int, input: Any) -> bytes:
def _run_command(self, service: int, method: int, input: Any) -> bytes:
input_bytes = input.SerializeToString()
try:
return self._backend.command(method, input_bytes)
return self._backend.command(service, method, input_bytes)
except Exception as e:
err_bytes = bytes(e.args[0])
err = pb.BackendError()

View File

@ -1,10 +1,13 @@
#!/usr/bin/env python3
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import os
import re
import sys
import google.protobuf.descriptor
import pylib.anki._backend.backend_pb2 as pb
import stringcase
@ -97,7 +100,7 @@ def get_input_assign(msg):
return ", ".join(f"{f.name}={f.name}" for f in fields)
def render_method(method, idx):
def render_method(service_idx, method_idx, method):
input_name = method.input_type.name
if (
(input_name.endswith("In") or len(method.input_type.fields) < 2)
@ -134,11 +137,11 @@ def render_method(method, idx):
{input_assign_outer}"""
if method.name in SKIP_DECODE:
buf += f"""return self._run_command({idx+1}, input)
buf += f"""return self._run_command({service_idx}, {method_idx+1}, input)
"""
else:
buf += f"""output = pb.{method.output_type.name}()
output.ParseFromString(self._run_command({idx+1}, input))
output.ParseFromString(self._run_command({service_idx}, {method_idx+1}, input))
return output{single_field}
"""
@ -146,13 +149,27 @@ def render_method(method, idx):
out = []
for idx, method in enumerate(pb._BACKENDSERVICE.methods):
out.append(render_method(method, idx))
def render_service(
service: google.protobuf.descriptor.ServiceDescriptor, service_index: int
) -> None:
for method_index, method in enumerate(service.methods):
out.append(render_method(service_index, method_index, method))
for service in pb.ServiceIndex.DESCRIPTOR.values:
# SERVICE_INDEX_TEST -> _TESTSERVICE
service_var = service.name.replace("SERVICE_INDEX", "") + "SERVICE"
service_obj = getattr(pb, service_var)
service_index = service.number
render_service(service_obj, service_index)
out = "\n".join(out)
sys.stdout.buffer.write(
open(sys.argv[1], "wb").write(
(
'''# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@ -174,7 +191,7 @@ from typing import *
import anki._backend.backend_pb2 as pb
class RustBackendGenerated:
def _run_command(self, method: int, input: Any) -> bytes:
def _run_command(self, service: int, method: int, input: Any) -> bytes:
raise Exception("not implemented")
'''

View File

@ -3,5 +3,5 @@ def open_backend(data: bytes) -> Backend: ...
class Backend:
@classmethod
def command(self, method: int, data: bytes) -> bytes: ...
def command(self, service: int, method: int, data: bytes) -> bytes: ...
def db_command(self, data: bytes) -> bytes: ...

View File

@ -1,15 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki::backend::{init_backend, Backend as RustBackend, BackendMethod};
use anki::backend::{init_backend, Backend as RustBackend};
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::{create_exception, wrap_pyfunction};
use std::convert::TryFrom;
// Regular backend
//////////////////////////////////
#[pyclass(module = "rsbridge")]
struct Backend {
@ -31,45 +27,17 @@ fn open_backend(init_msg: &PyBytes) -> PyResult<Backend> {
}
}
fn want_release_gil(method: u32) -> bool {
if let Ok(method) = BackendMethod::try_from(method) {
!matches!(
method,
BackendMethod::ExtractAVTags
| BackendMethod::ExtractLatex
| BackendMethod::RenderExistingCard
| BackendMethod::RenderUncommittedCard
| BackendMethod::StripAVTags
| BackendMethod::SchedTimingToday
| BackendMethod::AddOrUpdateDeckLegacy
| BackendMethod::NewDeckLegacy
| BackendMethod::NewDeckConfigLegacy
| BackendMethod::GetStockNotetypeLegacy
| BackendMethod::StudiedToday
| BackendMethod::TranslateString
| BackendMethod::FormatTimespan
| BackendMethod::LatestProgress
| BackendMethod::SetWantsAbort
| BackendMethod::I18nResources
| BackendMethod::JoinSearchNodes
| BackendMethod::ReplaceSearchNode
| BackendMethod::BuildSearchString
| BackendMethod::StateIsLeech
)
} else {
false
}
}
#[pymethods]
impl Backend {
fn command(&self, py: Python, method: u32, input: &PyBytes) -> PyResult<PyObject> {
fn command(
&self,
py: Python,
service: u32,
method: u32,
input: &PyBytes,
) -> PyResult<PyObject> {
let in_bytes = input.as_bytes();
if want_release_gil(method) {
py.allow_threads(|| self.backend.run_command_bytes(method, in_bytes))
} else {
self.backend.run_command_bytes(method, in_bytes)
}
py.allow_threads(|| self.backend.run_method(service, method, in_bytes))
.map(|out_bytes| {
let out_obj = PyBytes::new(py, &out_bytes);
out_obj.into()

View File

@ -72,33 +72,18 @@ message DeckConfigID {
int64 dcid = 1;
}
// New style RPC definitions
// Backend methods
///////////////////////////////////////////////////////////
service BackendService {
rpc LatestProgress(Empty) returns (Progress);
rpc SetWantsAbort(Empty) returns (Empty);
// card rendering
rpc ExtractAVTags(ExtractAVTagsIn) returns (ExtractAVTagsOut);
rpc ExtractLatex(ExtractLatexIn) returns (ExtractLatexOut);
rpc GetEmptyCards(Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardIn) returns (RenderCardOut);
rpc RenderUncommittedCard(RenderUncommittedCardIn) returns (RenderCardOut);
rpc StripAVTags(String) returns (String);
// searching
rpc BuildSearchString(SearchNode) returns (String);
rpc SearchCards(SearchCardsIn) returns (SearchCardsOut);
rpc SearchNotes(SearchNotesIn) returns (SearchNotesOut);
rpc JoinSearchNodes(JoinSearchNodesIn) returns (String);
rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String);
rpc FindAndReplace(FindAndReplaceIn) returns (UInt32);
// scheduling
/// while the protobuf descriptors expose the order services are defined in,
/// that information is not available in prost, so we define an enum to make
/// sure all clients agree on the same service indices
enum ServiceIndex {
SERVICE_INDEX_SCHEDULING = 0;
SERVICE_INDEX_BACKEND = 1;
}
service SchedulingService {
rpc SchedTimingToday(Empty) returns (SchedTimingTodayOut);
rpc StudiedToday(Empty) returns (String);
rpc StudiedTodayMessage(StudiedTodayMessageIn) returns (String);
@ -121,6 +106,29 @@ service BackendService {
rpc AnswerCard(AnswerCardIn) returns (Empty);
rpc UpgradeScheduler(Empty) returns (Empty);
rpc GetQueuedCards(GetQueuedCardsIn) returns (GetQueuedCardsOut);
}
service BackendService {
rpc LatestProgress(Empty) returns (Progress);
rpc SetWantsAbort(Empty) returns (Empty);
// card rendering
rpc ExtractAVTags(ExtractAVTagsIn) returns (ExtractAVTagsOut);
rpc ExtractLatex(ExtractLatexIn) returns (ExtractLatexOut);
rpc GetEmptyCards(Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardIn) returns (RenderCardOut);
rpc RenderUncommittedCard(RenderUncommittedCardIn) returns (RenderCardOut);
rpc StripAVTags(String) returns (String);
// searching
rpc BuildSearchString(SearchNode) returns (String);
rpc SearchCards(SearchCardsIn) returns (SearchCardsOut);
rpc SearchNotes(SearchNotesIn) returns (SearchNotesOut);
rpc JoinSearchNodes(JoinSearchNodesIn) returns (String);
rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String);
rpc FindAndReplace(FindAndReplaceIn) returns (UInt32);
// stats

View File

@ -6,32 +6,14 @@ use std::{env, fmt::Write};
struct CustomGenerator {}
fn write_method_enum(buf: &mut String, service: &prost_build::Service) {
buf.push_str(
r#"
use num_enum::TryFromPrimitive;
#[derive(PartialEq,TryFromPrimitive)]
#[repr(u32)]
pub enum BackendMethod {
"#,
);
for (idx, method) in service.methods.iter().enumerate() {
writeln!(buf, " {} = {},", method.proto_name, idx + 1).unwrap();
}
buf.push_str("}\n\n");
}
fn write_method_trait(buf: &mut String, service: &prost_build::Service) {
buf.push_str(
r#"
use prost::Message;
pub type BackendResult<T> = std::result::Result<T, crate::err::AnkiError>;
pub trait BackendService {
fn run_command_bytes2_inner(&self, method: u32, input: &[u8]) -> std::result::Result<Vec<u8>, crate::err::AnkiError> {
pub trait Service {
fn run_method(&self, method: u32, input: &[u8]) -> Result<Vec<u8>> {
match method {
"#,
);
for (idx, method) in service.methods.iter().enumerate() {
write!(
buf,
@ -58,7 +40,7 @@ pub trait BackendService {
buf,
concat!(
" fn {method_name}(&self, input: {input_type}) -> ",
"BackendResult<{output_type}>;\n"
"Result<{output_type}>;\n"
),
method_name = method.name,
input_type = method.input_type,
@ -71,8 +53,18 @@ pub trait BackendService {
impl prost_build::ServiceGenerator for CustomGenerator {
fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
write_method_enum(buf, &service);
write!(
buf,
"pub mod {name} {{
use super::*;
use prost::Message;
use crate::err::Result;
",
name = service.name.replace("Service", "").to_ascii_lowercase()
)
.unwrap();
write_method_trait(buf, &service);
buf.push('}');
}
}

View File

@ -13,13 +13,13 @@ mod scheduler;
mod search;
mod sync;
pub use crate::backend_proto::BackendMethod;
use self::scheduler::SchedulingService;
use crate::backend_proto::backend::Service as BackendService;
use crate::{
backend::dbproxy::db_command_bytes,
backend_proto as pb,
backend_proto::{
AddOrUpdateDeckConfigLegacyIn, BackendResult, Empty, RenderedTemplateReplacement,
},
backend_proto::{AddOrUpdateDeckConfigLegacyIn, Empty, RenderedTemplateReplacement},
card::{Card, CardID},
cloze::add_cloze_numbers_in_string,
collection::{open_collection, Collection},
@ -37,14 +37,8 @@ use crate::{
notetype::{
all_stock_notetypes, CardTemplateSchema11, NoteType, NoteTypeSchema11, RenderCardOutput,
},
scheduler::{
new::NewCardSortOrder,
parse_due_date_str,
states::{CardState, NextCardStates},
timespan::{answer_button_time, time_span},
},
scheduler::timespan::{answer_button_time, time_span},
search::{concatenate_searches, replace_search_node, write_nodes, Node},
stats::studied_today,
sync::{http::SyncRequest, LocalServer},
template::RenderedNode,
text::{extract_av_tags, sanitize_html_no_images, strip_av_tags, AVTag},
@ -55,7 +49,6 @@ use fluent::FluentValue;
use futures::future::AbortHandle;
use log::error;
use once_cell::sync::OnceCell;
use pb::BackendService;
use progress::{AbortHandleSlot, Progress};
use prost::Message;
use serde_json::Value as JsonValue;
@ -107,19 +100,19 @@ pub fn init_backend(init_msg: &[u8]) -> std::result::Result<Backend, String> {
}
impl BackendService for Backend {
fn latest_progress(&self, _input: Empty) -> BackendResult<pb::Progress> {
fn latest_progress(&self, _input: Empty) -> Result<pb::Progress> {
let progress = self.progress_state.lock().unwrap().last_progress;
Ok(progress_to_proto(progress, &self.i18n))
}
fn set_wants_abort(&self, _input: Empty) -> BackendResult<Empty> {
fn set_wants_abort(&self, _input: Empty) -> Result<Empty> {
self.progress_state.lock().unwrap().want_abort = true;
Ok(().into())
}
// card rendering
fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> BackendResult<pb::ExtractAvTagsOut> {
fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> Result<pb::ExtractAvTagsOut> {
let (text, tags) = extract_av_tags(&input.text, input.question_side);
let pt_tags = tags
.into_iter()
@ -151,7 +144,7 @@ impl BackendService for Backend {
})
}
fn extract_latex(&self, input: pb::ExtractLatexIn) -> BackendResult<pb::ExtractLatexOut> {
fn extract_latex(&self, input: pb::ExtractLatexIn) -> Result<pb::ExtractLatexOut> {
let func = if input.expand_clozes {
extract_latex_expanding_clozes
} else {
@ -193,10 +186,7 @@ impl BackendService for Backend {
})
}
fn render_existing_card(
&self,
input: pb::RenderExistingCardIn,
) -> BackendResult<pb::RenderCardOut> {
fn render_existing_card(&self, input: pb::RenderExistingCardIn) -> Result<pb::RenderCardOut> {
self.with_col(|col| {
col.render_existing_card(CardID(input.card_id), input.browser)
.map(Into::into)
@ -206,7 +196,7 @@ impl BackendService for Backend {
fn render_uncommitted_card(
&self,
input: pb::RenderUncommittedCardIn,
) -> BackendResult<pb::RenderCardOut> {
) -> Result<pb::RenderCardOut> {
let schema11: CardTemplateSchema11 = serde_json::from_slice(&input.template)?;
let template = schema11.into();
let mut note = input
@ -221,7 +211,7 @@ impl BackendService for Backend {
})
}
fn strip_av_tags(&self, input: pb::String) -> BackendResult<pb::String> {
fn strip_av_tags(&self, input: pb::String) -> Result<pb::String> {
Ok(pb::String {
val: strip_av_tags(&input.val).into(),
})
@ -277,7 +267,7 @@ impl BackendService for Backend {
Ok(replace_search_node(existing, replacement).into())
}
fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> BackendResult<pb::UInt32> {
fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> Result<pb::UInt32> {
let mut search = if input.regex {
input.search
} else {
@ -298,187 +288,23 @@ impl BackendService for Backend {
.map(|cnt| pb::UInt32 { val: cnt as u32 })
})
}
// scheduling
//-----------------------------------------------
/// This behaves like _updateCutoff() in older code - it also unburies at the start of
/// a new day.
fn sched_timing_today(&self, _input: pb::Empty) -> Result<pb::SchedTimingTodayOut> {
self.with_col(|col| {
let timing = col.timing_today()?;
col.unbury_if_day_rolled_over(timing)?;
Ok(timing.into())
})
}
/// Fetch data from DB and return rendered string.
fn studied_today(&self, _input: pb::Empty) -> BackendResult<pb::String> {
self.with_col(|col| col.studied_today().map(Into::into))
}
/// Message rendering only, for old graphs.
fn studied_today_message(&self, input: pb::StudiedTodayMessageIn) -> BackendResult<pb::String> {
Ok(studied_today(input.cards, input.seconds as f32, &self.i18n).into())
}
fn update_stats(&self, input: pb::UpdateStatsIn) -> BackendResult<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
let today = col.current_due_day(0)?;
let usn = col.usn()?;
col.update_deck_stats(today, usn, input).map(Into::into)
})
})
}
fn extend_limits(&self, input: pb::ExtendLimitsIn) -> BackendResult<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
let today = col.current_due_day(0)?;
let usn = col.usn()?;
col.extend_limits(
today,
usn,
input.deck_id.into(),
input.new_delta,
input.review_delta,
)
.map(Into::into)
})
})
}
fn counts_for_deck_today(&self, input: pb::DeckId) -> BackendResult<pb::CountsForDeckTodayOut> {
self.with_col(|col| col.counts_for_deck_today(input.did.into()))
}
fn congrats_info(&self, _input: Empty) -> BackendResult<pb::CongratsInfoOut> {
self.with_col(|col| col.congrats_info())
}
fn restore_buried_and_suspended_cards(&self, input: pb::CardIDs) -> BackendResult<Empty> {
let cids: Vec<_> = input.into();
self.with_col(|col| col.unbury_or_unsuspend_cards(&cids).map(Into::into))
}
fn unbury_cards_in_current_deck(
&self,
input: pb::UnburyCardsInCurrentDeckIn,
) -> BackendResult<Empty> {
self.with_col(|col| {
col.unbury_cards_in_current_deck(input.mode())
.map(Into::into)
})
}
fn bury_or_suspend_cards(&self, input: pb::BuryOrSuspendCardsIn) -> BackendResult<Empty> {
self.with_col(|col| {
let mode = input.mode();
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
col.bury_or_suspend_cards(&cids, mode).map(Into::into)
})
}
fn empty_filtered_deck(&self, input: pb::DeckId) -> BackendResult<Empty> {
self.with_col(|col| col.empty_filtered_deck(input.did.into()).map(Into::into))
}
fn rebuild_filtered_deck(&self, input: pb::DeckId) -> BackendResult<pb::UInt32> {
self.with_col(|col| col.rebuild_filtered_deck(input.did.into()).map(Into::into))
}
fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewIn) -> BackendResult<Empty> {
self.with_col(|col| {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let log = input.log;
col.reschedule_cards_as_new(&cids, log).map(Into::into)
})
}
fn set_due_date(&self, input: pb::SetDueDateIn) -> BackendResult<pb::Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let spec = parse_due_date_str(&input.days)?;
self.with_col(|col| col.set_due_date(&cids, spec).map(Into::into))
}
fn sort_cards(&self, input: pb::SortCardsIn) -> BackendResult<Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let (start, step, random, shift) = (
input.starting_from,
input.step_size,
input.randomize,
input.shift_existing,
);
let order = if random {
NewCardSortOrder::Random
} else {
NewCardSortOrder::Preserve
};
self.with_col(|col| {
col.sort_cards(&cids, start, step, order, shift)
.map(Into::into)
})
}
fn sort_deck(&self, input: pb::SortDeckIn) -> BackendResult<Empty> {
self.with_col(|col| {
col.sort_deck(input.deck_id.into(), input.randomize)
.map(Into::into)
})
}
fn get_next_card_states(&self, input: pb::CardId) -> BackendResult<pb::NextCardStates> {
let cid: CardID = input.into();
self.with_col(|col| col.get_next_card_states(cid))
.map(Into::into)
}
fn describe_next_states(&self, input: pb::NextCardStates) -> BackendResult<pb::StringList> {
let states: NextCardStates = input.into();
self.with_col(|col| col.describe_next_states(states))
.map(Into::into)
}
fn state_is_leech(&self, input: pb::SchedulingState) -> BackendResult<pb::Bool> {
let state: CardState = input.into();
Ok(state.leeched().into())
}
fn answer_card(&self, input: pb::AnswerCardIn) -> BackendResult<pb::Empty> {
self.with_col(|col| col.answer_card(&input.into()))
.map(Into::into)
}
fn upgrade_scheduler(&self, _input: Empty) -> BackendResult<Empty> {
self.with_col(|col| col.transact(None, |col| col.upgrade_to_v2_scheduler()))
.map(Into::into)
}
fn get_queued_cards(
&self,
input: pb::GetQueuedCardsIn,
) -> BackendResult<pb::GetQueuedCardsOut> {
self.with_col(|col| col.get_queued_cards(input.fetch_limit, input.intraday_learning_only))
}
// statistics
//-----------------------------------------------
fn card_stats(&self, input: pb::CardId) -> BackendResult<pb::String> {
fn card_stats(&self, input: pb::CardId) -> Result<pb::String> {
self.with_col(|col| col.card_stats(input.into()))
.map(Into::into)
}
fn graphs(&self, input: pb::GraphsIn) -> BackendResult<pb::GraphsOut> {
fn graphs(&self, input: pb::GraphsIn) -> Result<pb::GraphsOut> {
self.with_col(|col| col.graph_data_for_search(&input.search, input.days))
}
fn get_graph_preferences(&self, _input: pb::Empty) -> BackendResult<pb::GraphPreferences> {
fn get_graph_preferences(&self, _input: pb::Empty) -> Result<pb::GraphPreferences> {
self.with_col(|col| col.get_graph_preferences())
}
fn set_graph_preferences(&self, input: pb::GraphPreferences) -> BackendResult<Empty> {
fn set_graph_preferences(&self, input: pb::GraphPreferences) -> Result<Empty> {
self.with_col(|col| col.set_graph_preferences(input))
.map(Into::into)
}
@ -508,7 +334,7 @@ impl BackendService for Backend {
})
}
fn trash_media_files(&self, input: pb::TrashMediaFilesIn) -> BackendResult<Empty> {
fn trash_media_files(&self, input: pb::TrashMediaFilesIn) -> Result<Empty> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
@ -517,7 +343,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn add_media_file(&self, input: pb::AddMediaFileIn) -> BackendResult<pb::String> {
fn add_media_file(&self, input: pb::AddMediaFileIn) -> Result<pb::String> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
@ -528,7 +354,7 @@ impl BackendService for Backend {
})
}
fn empty_trash(&self, _input: Empty) -> BackendResult<Empty> {
fn empty_trash(&self, _input: Empty) -> Result<Empty> {
let mut handler = self.new_progress_handler();
let progress_fn =
move |progress| handler.update(Progress::MediaCheck(progress as u32), true);
@ -544,7 +370,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn restore_trash(&self, _input: Empty) -> BackendResult<Empty> {
fn restore_trash(&self, _input: Empty) -> Result<Empty> {
let mut handler = self.new_progress_handler();
let progress_fn =
move |progress| handler.update(Progress::MediaCheck(progress as u32), true);
@ -595,7 +421,7 @@ impl BackendService for Backend {
})
}
fn deck_tree_legacy(&self, _input: pb::Empty) -> BackendResult<pb::Json> {
fn deck_tree_legacy(&self, _input: pb::Empty) -> Result<pb::Json> {
self.with_col(|col| {
let tree = col.legacy_deck_tree()?;
serde_json::to_vec(&tree)
@ -604,7 +430,7 @@ impl BackendService for Backend {
})
}
fn get_all_decks_legacy(&self, _input: Empty) -> BackendResult<pb::Json> {
fn get_all_decks_legacy(&self, _input: Empty) -> Result<pb::Json> {
self.with_col(|col| {
let decks = col.storage.get_all_decks_as_schema11()?;
serde_json::to_vec(&decks).map_err(Into::into)
@ -650,7 +476,7 @@ impl BackendService for Backend {
})
}
fn new_deck_legacy(&self, input: pb::Bool) -> BackendResult<pb::Json> {
fn new_deck_legacy(&self, input: pb::Bool) -> Result<pb::Json> {
let deck = if input.val {
Deck::new_filtered()
} else {
@ -662,12 +488,12 @@ impl BackendService for Backend {
.map(Into::into)
}
fn remove_deck(&self, input: pb::DeckId) -> BackendResult<Empty> {
fn remove_deck(&self, input: pb::DeckId) -> Result<Empty> {
self.with_col(|col| col.remove_deck_and_child_decks(input.into()))
.map(Into::into)
}
fn drag_drop_decks(&self, input: pb::DragDropDecksIn) -> BackendResult<Empty> {
fn drag_drop_decks(&self, input: pb::DragDropDecksIn) -> Result<Empty> {
let source_dids: Vec<_> = input.source_deck_ids.into_iter().map(Into::into).collect();
let target_did = if input.target_deck_id == 0 {
None
@ -684,7 +510,7 @@ impl BackendService for Backend {
fn add_or_update_deck_config_legacy(
&self,
input: AddOrUpdateDeckConfigLegacyIn,
) -> BackendResult<pb::DeckConfigId> {
) -> Result<pb::DeckConfigId> {
let conf: DeckConfSchema11 = serde_json::from_slice(&input.config)?;
let mut conf: DeckConf = conf.into();
self.with_col(|col| {
@ -696,7 +522,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn all_deck_config_legacy(&self, _input: Empty) -> BackendResult<pb::Json> {
fn all_deck_config_legacy(&self, _input: Empty) -> Result<pb::Json> {
self.with_col(|col| {
let conf: Vec<DeckConfSchema11> = col
.storage
@ -709,7 +535,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn get_deck_config_legacy(&self, input: pb::DeckConfigId) -> BackendResult<pb::Json> {
fn get_deck_config_legacy(&self, input: pb::DeckConfigId) -> Result<pb::Json> {
self.with_col(|col| {
let conf = col.get_deck_config(input.into(), true)?.unwrap();
let conf: DeckConfSchema11 = conf.into();
@ -718,13 +544,13 @@ impl BackendService for Backend {
.map(Into::into)
}
fn new_deck_config_legacy(&self, _input: Empty) -> BackendResult<pb::Json> {
fn new_deck_config_legacy(&self, _input: Empty) -> Result<pb::Json> {
serde_json::to_vec(&DeckConfSchema11::default())
.map_err(Into::into)
.map(Into::into)
}
fn remove_deck_config(&self, input: pb::DeckConfigId) -> BackendResult<Empty> {
fn remove_deck_config(&self, input: pb::DeckConfigId) -> Result<Empty> {
self.with_col(|col| col.transact(None, |col| col.remove_deck_config(input.into())))
.map(Into::into)
}
@ -732,7 +558,7 @@ impl BackendService for Backend {
// cards
//-------------------------------------------------------------------
fn get_card(&self, input: pb::CardId) -> BackendResult<pb::Card> {
fn get_card(&self, input: pb::CardId) -> Result<pb::Card> {
self.with_col(|col| {
col.storage
.get_card(input.into())
@ -741,7 +567,7 @@ impl BackendService for Backend {
})
}
fn update_card(&self, input: pb::UpdateCardIn) -> BackendResult<Empty> {
fn update_card(&self, input: pb::UpdateCardIn) -> Result<Empty> {
self.with_col(|col| {
let op = if input.skip_undo_entry {
None
@ -754,7 +580,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn remove_cards(&self, input: pb::RemoveCardsIn) -> BackendResult<Empty> {
fn remove_cards(&self, input: pb::RemoveCardsIn) -> Result<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
col.remove_cards_and_orphaned_notes(
@ -769,7 +595,7 @@ impl BackendService for Backend {
})
}
fn set_deck(&self, input: pb::SetDeckIn) -> BackendResult<Empty> {
fn set_deck(&self, input: pb::SetDeckIn) -> Result<Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let deck_id = input.deck_id.into();
self.with_col(|col| col.set_deck(&cids, deck_id).map(Into::into))
@ -778,14 +604,14 @@ impl BackendService for Backend {
// notes
//-------------------------------------------------------------------
fn new_note(&self, input: pb::NoteTypeId) -> BackendResult<pb::Note> {
fn new_note(&self, input: pb::NoteTypeId) -> Result<pb::Note> {
self.with_col(|col| {
let nt = col.get_notetype(input.into())?.ok_or(AnkiError::NotFound)?;
Ok(nt.new_note().into())
})
}
fn add_note(&self, input: pb::AddNoteIn) -> BackendResult<pb::NoteId> {
fn add_note(&self, input: pb::AddNoteIn) -> Result<pb::NoteId> {
self.with_col(|col| {
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
col.add_note(&mut note, DeckID(input.deck_id))
@ -793,17 +619,14 @@ impl BackendService for Backend {
})
}
fn defaults_for_adding(
&self,
input: pb::DefaultsForAddingIn,
) -> BackendResult<pb::DeckAndNotetype> {
fn defaults_for_adding(&self, input: pb::DefaultsForAddingIn) -> Result<pb::DeckAndNotetype> {
self.with_col(|col| {
let home_deck: DeckID = input.home_deck_of_current_review_card.into();
col.defaults_for_adding(home_deck).map(Into::into)
})
}
fn default_deck_for_notetype(&self, input: pb::NoteTypeId) -> BackendResult<pb::DeckId> {
fn default_deck_for_notetype(&self, input: pb::NoteTypeId) -> Result<pb::DeckId> {
self.with_col(|col| {
Ok(col
.default_deck_for_notetype(input.into())?
@ -812,7 +635,7 @@ impl BackendService for Backend {
})
}
fn update_note(&self, input: pb::UpdateNoteIn) -> BackendResult<Empty> {
fn update_note(&self, input: pb::UpdateNoteIn) -> Result<Empty> {
self.with_col(|col| {
let op = if input.skip_undo_entry {
None
@ -825,7 +648,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn get_note(&self, input: pb::NoteId) -> BackendResult<pb::Note> {
fn get_note(&self, input: pb::NoteId) -> Result<pb::Note> {
self.with_col(|col| {
col.storage
.get_note(input.into())?
@ -834,7 +657,7 @@ impl BackendService for Backend {
})
}
fn remove_notes(&self, input: pb::RemoveNotesIn) -> BackendResult<Empty> {
fn remove_notes(&self, input: pb::RemoveNotesIn) -> Result<Empty> {
self.with_col(|col| {
if !input.note_ids.is_empty() {
col.remove_notes(
@ -859,7 +682,7 @@ impl BackendService for Backend {
})
}
fn add_note_tags(&self, input: pb::AddNoteTagsIn) -> BackendResult<pb::UInt32> {
fn add_note_tags(&self, input: pb::AddNoteTagsIn) -> Result<pb::UInt32> {
self.with_col(|col| {
col.add_tags_to_notes(&to_nids(input.nids), &input.tags)
.map(|n| n as u32)
@ -867,7 +690,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn update_note_tags(&self, input: pb::UpdateNoteTagsIn) -> BackendResult<pb::UInt32> {
fn update_note_tags(&self, input: pb::UpdateNoteTagsIn) -> Result<pb::UInt32> {
self.with_col(|col| {
col.replace_tags_for_notes(
&to_nids(input.nids),
@ -879,7 +702,7 @@ impl BackendService for Backend {
})
}
fn cloze_numbers_in_note(&self, note: pb::Note) -> BackendResult<pb::ClozeNumbersInNoteOut> {
fn cloze_numbers_in_note(&self, note: pb::Note) -> Result<pb::ClozeNumbersInNoteOut> {
let mut set = HashSet::with_capacity(4);
for field in &note.fields {
add_cloze_numbers_in_string(field, &mut set);
@ -889,7 +712,7 @@ impl BackendService for Backend {
})
}
fn after_note_updates(&self, input: pb::AfterNoteUpdatesIn) -> BackendResult<Empty> {
fn after_note_updates(&self, input: pb::AfterNoteUpdatesIn) -> Result<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
col.after_note_updates(
@ -905,7 +728,7 @@ impl BackendService for Backend {
fn field_names_for_notes(
&self,
input: pb::FieldNamesForNotesIn,
) -> BackendResult<pb::FieldNamesForNotesOut> {
) -> Result<pb::FieldNamesForNotesOut> {
self.with_col(|col| {
let nids: Vec<_> = input.nids.into_iter().map(NoteID).collect();
col.storage
@ -914,10 +737,7 @@ impl BackendService for Backend {
})
}
fn note_is_duplicate_or_empty(
&self,
input: pb::Note,
) -> BackendResult<pb::NoteIsDuplicateOrEmptyOut> {
fn note_is_duplicate_or_empty(&self, input: pb::Note) -> Result<pb::NoteIsDuplicateOrEmptyOut> {
let note: Note = input.into();
self.with_col(|col| {
col.note_is_duplicate_or_empty(&note)
@ -925,7 +745,7 @@ impl BackendService for Backend {
})
}
fn cards_of_note(&self, input: pb::NoteId) -> BackendResult<pb::CardIDs> {
fn cards_of_note(&self, input: pb::NoteId) -> Result<pb::CardIDs> {
self.with_col(|col| {
col.storage
.all_card_ids_of_note(NoteID(input.nid))
@ -938,10 +758,7 @@ impl BackendService for Backend {
// notetypes
//-------------------------------------------------------------------
fn add_or_update_notetype(
&self,
input: pb::AddOrUpdateNotetypeIn,
) -> BackendResult<pb::NoteTypeId> {
fn add_or_update_notetype(&self, input: pb::AddOrUpdateNotetypeIn) -> Result<pb::NoteTypeId> {
self.with_col(|col| {
let legacy: NoteTypeSchema11 = serde_json::from_slice(&input.json)?;
let mut nt: NoteType = legacy.into();
@ -954,7 +771,7 @@ impl BackendService for Backend {
})
}
fn get_stock_notetype_legacy(&self, input: pb::StockNoteType) -> BackendResult<pb::Json> {
fn get_stock_notetype_legacy(&self, input: pb::StockNoteType) -> Result<pb::Json> {
// fixme: use individual functions instead of full vec
let mut all = all_stock_notetypes(&self.i18n);
let idx = (input.kind as usize).min(all.len() - 1);
@ -965,7 +782,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn get_notetype_legacy(&self, input: pb::NoteTypeId) -> BackendResult<pb::Json> {
fn get_notetype_legacy(&self, input: pb::NoteTypeId) -> Result<pb::Json> {
self.with_col(|col| {
let schema11: NoteTypeSchema11 = col
.storage
@ -976,7 +793,7 @@ impl BackendService for Backend {
})
}
fn get_notetype_names(&self, _input: Empty) -> BackendResult<pb::NoteTypeNames> {
fn get_notetype_names(&self, _input: Empty) -> Result<pb::NoteTypeNames> {
self.with_col(|col| {
let entries: Vec<_> = col
.storage
@ -988,7 +805,7 @@ impl BackendService for Backend {
})
}
fn get_notetype_names_and_counts(&self, _input: Empty) -> BackendResult<pb::NoteTypeUseCounts> {
fn get_notetype_names_and_counts(&self, _input: Empty) -> Result<pb::NoteTypeUseCounts> {
self.with_col(|col| {
let entries: Vec<_> = col
.storage
@ -1004,7 +821,7 @@ impl BackendService for Backend {
})
}
fn get_notetype_id_by_name(&self, input: pb::String) -> BackendResult<pb::NoteTypeId> {
fn get_notetype_id_by_name(&self, input: pb::String) -> Result<pb::NoteTypeId> {
self.with_col(|col| {
col.storage
.get_notetype_id(&input.val)
@ -1013,7 +830,7 @@ impl BackendService for Backend {
})
}
fn remove_notetype(&self, input: pb::NoteTypeId) -> BackendResult<Empty> {
fn remove_notetype(&self, input: pb::NoteTypeId) -> Result<Empty> {
self.with_col(|col| col.remove_notetype(input.into()))
.map(Into::into)
}
@ -1021,7 +838,7 @@ impl BackendService for Backend {
// collection
//-------------------------------------------------------------------
fn open_collection(&self, input: pb::OpenCollectionIn) -> BackendResult<Empty> {
fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<Empty> {
let mut col = self.col.lock().unwrap();
if col.is_some() {
return Err(AnkiError::CollectionAlreadyOpen);
@ -1050,7 +867,7 @@ impl BackendService for Backend {
Ok(().into())
}
fn close_collection(&self, input: pb::CloseCollectionIn) -> BackendResult<Empty> {
fn close_collection(&self, input: pb::CloseCollectionIn) -> Result<Empty> {
self.abort_media_sync_and_wait();
let mut col = self.col.lock().unwrap();
@ -1069,7 +886,7 @@ impl BackendService for Backend {
Ok(().into())
}
fn check_database(&self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
fn check_database(&self, _input: pb::Empty) -> Result<pb::CheckDatabaseOut> {
let mut handler = self.new_progress_handler();
let progress_fn = move |progress, throttle| {
handler.update(Progress::DatabaseCheck(progress), throttle);
@ -1103,11 +920,11 @@ impl BackendService for Backend {
// sync
//-------------------------------------------------------------------
fn sync_media(&self, input: pb::SyncAuth) -> BackendResult<Empty> {
fn sync_media(&self, input: pb::SyncAuth) -> Result<Empty> {
self.sync_media_inner(input).map(Into::into)
}
fn abort_sync(&self, _input: Empty) -> BackendResult<Empty> {
fn abort_sync(&self, _input: Empty) -> Result<Empty> {
if let Some(handle) = self.sync_abort.lock().unwrap().take() {
handle.abort();
}
@ -1115,7 +932,7 @@ impl BackendService for Backend {
}
/// Abort the media sync. Does not wait for completion.
fn abort_media_sync(&self, _input: Empty) -> BackendResult<Empty> {
fn abort_media_sync(&self, _input: Empty) -> Result<Empty> {
let guard = self.state.lock().unwrap();
if let Some(handle) = &guard.media_sync_abort {
handle.abort();
@ -1123,33 +940,33 @@ impl BackendService for Backend {
Ok(().into())
}
fn before_upload(&self, _input: Empty) -> BackendResult<Empty> {
fn before_upload(&self, _input: Empty) -> Result<Empty> {
self.with_col(|col| col.before_upload().map(Into::into))
}
fn sync_login(&self, input: pb::SyncLoginIn) -> BackendResult<pb::SyncAuth> {
fn sync_login(&self, input: pb::SyncLoginIn) -> Result<pb::SyncAuth> {
self.sync_login_inner(input)
}
fn sync_status(&self, input: pb::SyncAuth) -> BackendResult<pb::SyncStatusOut> {
fn sync_status(&self, input: pb::SyncAuth) -> Result<pb::SyncStatusOut> {
self.sync_status_inner(input)
}
fn sync_collection(&self, input: pb::SyncAuth) -> BackendResult<pb::SyncCollectionOut> {
fn sync_collection(&self, input: pb::SyncAuth) -> Result<pb::SyncCollectionOut> {
self.sync_collection_inner(input)
}
fn full_upload(&self, input: pb::SyncAuth) -> BackendResult<Empty> {
fn full_upload(&self, input: pb::SyncAuth) -> Result<Empty> {
self.full_sync_inner(input, true)?;
Ok(().into())
}
fn full_download(&self, input: pb::SyncAuth) -> BackendResult<Empty> {
fn full_download(&self, input: pb::SyncAuth) -> Result<Empty> {
self.full_sync_inner(input, false)?;
Ok(().into())
}
fn sync_server_method(&self, input: pb::SyncServerMethodIn) -> BackendResult<pb::Json> {
fn sync_server_method(&self, input: pb::SyncServerMethodIn) -> Result<pb::Json> {
let req = SyncRequest::from_method_and_data(input.method(), input.data)?;
self.sync_server_method_inner(req).map(Into::into)
}
@ -1157,7 +974,7 @@ impl BackendService for Backend {
// i18n/messages
//-------------------------------------------------------------------
fn translate_string(&self, input: pb::TranslateStringIn) -> BackendResult<pb::String> {
fn translate_string(&self, input: pb::TranslateStringIn) -> Result<pb::String> {
let key = match crate::fluent_proto::FluentString::from_i32(input.key) {
Some(key) => key,
None => return Ok("invalid key".to_string().into()),
@ -1172,7 +989,7 @@ impl BackendService for Backend {
Ok(self.i18n.trn(key, map).into())
}
fn format_timespan(&self, input: pb::FormatTimespanIn) -> BackendResult<pb::String> {
fn format_timespan(&self, input: pb::FormatTimespanIn) -> Result<pb::String> {
use pb::format_timespan_in::Context;
Ok(match input.context() {
Context::Precise => time_span(input.seconds, &self.i18n, true),
@ -1182,13 +999,13 @@ impl BackendService for Backend {
.into())
}
fn i18n_resources(&self, _input: Empty) -> BackendResult<pb::Json> {
fn i18n_resources(&self, _input: Empty) -> Result<pb::Json> {
serde_json::to_vec(&self.i18n.resources_for_js())
.map(Into::into)
.map_err(Into::into)
}
fn render_markdown(&self, input: pb::RenderMarkdownIn) -> BackendResult<pb::String> {
fn render_markdown(&self, input: pb::RenderMarkdownIn) -> Result<pb::String> {
let mut text = render_markdown(&input.markdown);
if input.sanitize {
// currently no images
@ -1200,11 +1017,11 @@ impl BackendService for Backend {
// tags
//-------------------------------------------------------------------
fn clear_unused_tags(&self, _input: pb::Empty) -> BackendResult<pb::Empty> {
fn clear_unused_tags(&self, _input: pb::Empty) -> Result<pb::Empty> {
self.with_col(|col| col.transact(None, |col| col.clear_unused_tags().map(Into::into)))
}
fn all_tags(&self, _input: Empty) -> BackendResult<pb::StringList> {
fn all_tags(&self, _input: Empty) -> Result<pb::StringList> {
Ok(pb::StringList {
vals: self.with_col(|col| {
Ok(col
@ -1217,7 +1034,7 @@ impl BackendService for Backend {
})
}
fn set_tag_expanded(&self, input: pb::SetTagExpandedIn) -> BackendResult<pb::Empty> {
fn set_tag_expanded(&self, input: pb::SetTagExpandedIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| {
col.set_tag_expanded(&input.name, input.expanded)?;
@ -1226,7 +1043,7 @@ impl BackendService for Backend {
})
}
fn clear_tag(&self, tag: pb::String) -> BackendResult<pb::Empty> {
fn clear_tag(&self, tag: pb::String) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| {
col.storage.clear_tag_and_children(tag.val.as_str())?;
@ -1239,7 +1056,7 @@ impl BackendService for Backend {
self.with_col(|col| col.tag_tree())
}
fn drag_drop_tags(&self, input: pb::DragDropTagsIn) -> BackendResult<Empty> {
fn drag_drop_tags(&self, input: pb::DragDropTagsIn) -> Result<Empty> {
let source_tags = input.source_tags;
let target_tag = if input.target_tag.is_empty() {
None
@ -1253,7 +1070,7 @@ impl BackendService for Backend {
// config/preferences
//-------------------------------------------------------------------
fn get_config_json(&self, input: pb::String) -> BackendResult<pb::Json> {
fn get_config_json(&self, input: pb::String) -> Result<pb::Json> {
self.with_col(|col| {
let val: Option<JsonValue> = col.get_config_optional(input.val.as_str());
val.ok_or(AnkiError::NotFound)
@ -1262,7 +1079,7 @@ impl BackendService for Backend {
})
}
fn set_config_json(&self, input: pb::SetConfigJsonIn) -> BackendResult<Empty> {
fn set_config_json(&self, input: pb::SetConfigJsonIn) -> Result<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
// ensure it's a well-formed object
@ -1273,12 +1090,12 @@ impl BackendService for Backend {
.map(Into::into)
}
fn remove_config(&self, input: pb::String) -> BackendResult<Empty> {
fn remove_config(&self, input: pb::String) -> Result<Empty> {
self.with_col(|col| col.transact(None, |col| col.remove_config(input.val.as_str())))
.map(Into::into)
}
fn get_all_config(&self, _input: Empty) -> BackendResult<pb::Json> {
fn get_all_config(&self, _input: Empty) -> Result<pb::Json> {
self.with_col(|col| {
let conf = col.storage.get_all_config()?;
serde_json::to_vec(&conf).map_err(Into::into)
@ -1286,7 +1103,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn get_config_bool(&self, input: pb::config::Bool) -> BackendResult<pb::Bool> {
fn get_config_bool(&self, input: pb::config::Bool) -> Result<pb::Bool> {
self.with_col(|col| {
Ok(pb::Bool {
val: col.get_bool(input.key().into()),
@ -1294,12 +1111,12 @@ impl BackendService for Backend {
})
}
fn set_config_bool(&self, input: pb::SetConfigBoolIn) -> BackendResult<pb::Empty> {
fn set_config_bool(&self, input: pb::SetConfigBoolIn) -> Result<pb::Empty> {
self.with_col(|col| col.transact(None, |col| col.set_bool(input.key().into(), input.value)))
.map(Into::into)
}
fn get_config_string(&self, input: pb::config::String) -> BackendResult<pb::String> {
fn get_config_string(&self, input: pb::config::String) -> Result<pb::String> {
self.with_col(|col| {
Ok(pb::String {
val: col.get_string(input.key().into()),
@ -1307,18 +1124,18 @@ impl BackendService for Backend {
})
}
fn set_config_string(&self, input: pb::SetConfigStringIn) -> BackendResult<pb::Empty> {
fn set_config_string(&self, input: pb::SetConfigStringIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| col.set_string(input.key().into(), &input.value))
})
.map(Into::into)
}
fn get_preferences(&self, _input: Empty) -> BackendResult<pb::Preferences> {
fn get_preferences(&self, _input: Empty) -> Result<pb::Preferences> {
self.with_col(|col| col.get_preferences())
}
fn set_preferences(&self, input: pb::Preferences) -> BackendResult<Empty> {
fn set_preferences(&self, input: pb::Preferences) -> Result<Empty> {
self.with_col(|col| col.set_preferences(input))
.map(Into::into)
}
@ -1344,8 +1161,19 @@ impl Backend {
&self.i18n
}
pub fn run_command_bytes(&self, method: u32, input: &[u8]) -> result::Result<Vec<u8>, Vec<u8>> {
self.run_command_bytes2_inner(method, input).map_err(|err| {
pub fn run_method(
&self,
service: u32,
method: u32,
input: &[u8],
) -> result::Result<Vec<u8>, Vec<u8>> {
pb::ServiceIndex::from_i32(service as i32)
.ok_or_else(|| AnkiError::invalid_input("invalid service"))
.and_then(|service| match service {
pb::ServiceIndex::Scheduling => SchedulingService::run_method(self, method, input),
pb::ServiceIndex::Backend => BackendService::run_method(self, method, input),
})
.map_err(|err| {
let backend_err = anki_error_to_proto_error(err, &self.i18n);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();

View File

@ -3,3 +3,175 @@
mod answering;
mod states;
use super::Backend;
use crate::{
backend_proto::{self as pb},
prelude::*,
scheduler::{
new::NewCardSortOrder,
parse_due_date_str,
states::{CardState, NextCardStates},
},
stats::studied_today,
};
pub(super) use pb::scheduling::Service as SchedulingService;
impl SchedulingService for Backend {
/// This behaves like _updateCutoff() in older code - it also unburies at the start of
/// a new day.
fn sched_timing_today(&self, _input: pb::Empty) -> Result<pb::SchedTimingTodayOut> {
self.with_col(|col| {
let timing = col.timing_today()?;
col.unbury_if_day_rolled_over(timing)?;
Ok(timing.into())
})
}
/// Fetch data from DB and return rendered string.
fn studied_today(&self, _input: pb::Empty) -> Result<pb::String> {
self.with_col(|col| col.studied_today().map(Into::into))
}
/// Message rendering only, for old graphs.
fn studied_today_message(&self, input: pb::StudiedTodayMessageIn) -> Result<pb::String> {
Ok(studied_today(input.cards, input.seconds as f32, &self.i18n).into())
}
fn update_stats(&self, input: pb::UpdateStatsIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| {
let today = col.current_due_day(0)?;
let usn = col.usn()?;
col.update_deck_stats(today, usn, input).map(Into::into)
})
})
}
fn extend_limits(&self, input: pb::ExtendLimitsIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| {
let today = col.current_due_day(0)?;
let usn = col.usn()?;
col.extend_limits(
today,
usn,
input.deck_id.into(),
input.new_delta,
input.review_delta,
)
.map(Into::into)
})
})
}
fn counts_for_deck_today(&self, input: pb::DeckId) -> Result<pb::CountsForDeckTodayOut> {
self.with_col(|col| col.counts_for_deck_today(input.did.into()))
}
fn congrats_info(&self, _input: pb::Empty) -> Result<pb::CongratsInfoOut> {
self.with_col(|col| col.congrats_info())
}
fn restore_buried_and_suspended_cards(&self, input: pb::CardIDs) -> Result<pb::Empty> {
let cids: Vec<_> = input.into();
self.with_col(|col| col.unbury_or_unsuspend_cards(&cids).map(Into::into))
}
fn unbury_cards_in_current_deck(
&self,
input: pb::UnburyCardsInCurrentDeckIn,
) -> Result<pb::Empty> {
self.with_col(|col| {
col.unbury_cards_in_current_deck(input.mode())
.map(Into::into)
})
}
fn bury_or_suspend_cards(&self, input: pb::BuryOrSuspendCardsIn) -> Result<pb::Empty> {
self.with_col(|col| {
let mode = input.mode();
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
col.bury_or_suspend_cards(&cids, mode).map(Into::into)
})
}
fn empty_filtered_deck(&self, input: pb::DeckId) -> Result<pb::Empty> {
self.with_col(|col| col.empty_filtered_deck(input.did.into()).map(Into::into))
}
fn rebuild_filtered_deck(&self, input: pb::DeckId) -> Result<pb::UInt32> {
self.with_col(|col| col.rebuild_filtered_deck(input.did.into()).map(Into::into))
}
fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewIn) -> Result<pb::Empty> {
self.with_col(|col| {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let log = input.log;
col.reschedule_cards_as_new(&cids, log).map(Into::into)
})
}
fn set_due_date(&self, input: pb::SetDueDateIn) -> Result<pb::Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let spec = parse_due_date_str(&input.days)?;
self.with_col(|col| col.set_due_date(&cids, spec).map(Into::into))
}
fn sort_cards(&self, input: pb::SortCardsIn) -> Result<pb::Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let (start, step, random, shift) = (
input.starting_from,
input.step_size,
input.randomize,
input.shift_existing,
);
let order = if random {
NewCardSortOrder::Random
} else {
NewCardSortOrder::Preserve
};
self.with_col(|col| {
col.sort_cards(&cids, start, step, order, shift)
.map(Into::into)
})
}
fn sort_deck(&self, input: pb::SortDeckIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.sort_deck(input.deck_id.into(), input.randomize)
.map(Into::into)
})
}
fn get_next_card_states(&self, input: pb::CardId) -> Result<pb::NextCardStates> {
let cid: CardID = input.into();
self.with_col(|col| col.get_next_card_states(cid))
.map(Into::into)
}
fn describe_next_states(&self, input: pb::NextCardStates) -> Result<pb::StringList> {
let states: NextCardStates = input.into();
self.with_col(|col| col.describe_next_states(states))
.map(Into::into)
}
fn state_is_leech(&self, input: pb::SchedulingState) -> Result<pb::Bool> {
let state: CardState = input.into();
Ok(state.leeched().into())
}
fn answer_card(&self, input: pb::AnswerCardIn) -> Result<pb::Empty> {
self.with_col(|col| col.answer_card(&input.into()))
.map(Into::into)
}
fn upgrade_scheduler(&self, _input: pb::Empty) -> Result<pb::Empty> {
self.with_col(|col| col.transact(None, |col| col.upgrade_to_v2_scheduler()))
.map(Into::into)
}
fn get_queued_cards(&self, input: pb::GetQueuedCardsIn) -> Result<pb::GetQueuedCardsOut> {
self.with_col(|col| col.get_queued_cards(input.fetch_limit, input.intraday_learning_only))
}
}