From 4bd120cc4b9f582034a40a51033548aff3304e56 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 11 Mar 2021 16:53:36 +1000 Subject: [PATCH] split out remaining rpc methods @david-allison-1 note this also changes the method index to start at 0 instead of 1 --- pylib/anki/_backend/genbackend.py | 4 +- rslib/backend.proto | 39 ++-- rslib/build/protobuf.rs | 2 +- rslib/src/backend/card.rs | 74 ++++++- rslib/src/backend/cardrendering.rs | 48 ++++- rslib/src/backend/collection.rs | 104 ++++++++++ rslib/src/backend/i18n.rs | 55 +++++ rslib/src/backend/mod.rs | 315 ++--------------------------- rslib/src/backend/scheduler/mod.rs | 9 + 9 files changed, 331 insertions(+), 319 deletions(-) create mode 100644 rslib/src/backend/collection.rs create mode 100644 rslib/src/backend/i18n.rs diff --git a/pylib/anki/_backend/genbackend.py b/pylib/anki/_backend/genbackend.py index 633fe1f88..64fff6da6 100755 --- a/pylib/anki/_backend/genbackend.py +++ b/pylib/anki/_backend/genbackend.py @@ -137,11 +137,11 @@ def render_method(service_idx, method_idx, method): {input_assign_outer}""" if method.name in SKIP_DECODE: - buf += f"""return self._run_command({service_idx}, {method_idx+1}, input) + buf += f"""return self._run_command({service_idx}, {method_idx}, input) """ else: buf += f"""output = pb.{method.output_type.name}() - output.ParseFromString(self._run_command({service_idx}, {method_idx+1}, input)) + output.ParseFromString(self._run_command({service_idx}, {method_idx}, input)) return output{single_field} """ diff --git a/rslib/backend.proto b/rslib/backend.proto index a362810d8..50a7db282 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -77,7 +77,7 @@ message DeckConfigID { /// 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 +/// sure all clients agree on the service index enum ServiceIndex { SERVICE_INDEX_SCHEDULING = 0; SERVICE_INDEX_DECKS = 1; @@ -91,7 +91,9 @@ enum ServiceIndex { SERVICE_INDEX_SEARCH = 9; SERVICE_INDEX_STATS = 10; SERVICE_INDEX_MEDIA = 11; - SERVICE_INDEX_BACKEND = 99; + SERVICE_INDEX_I18N = 12; + SERVICE_INDEX_COLLECTION = 13; + SERVICE_INDEX_CARDS = 14; } service SchedulingService { @@ -192,6 +194,7 @@ service CardRenderingService { rpc RenderExistingCard(RenderExistingCardIn) returns (RenderCardOut); rpc RenderUncommittedCard(RenderUncommittedCardIn) returns (RenderCardOut); rpc StripAVTags(String) returns (String); + rpc RenderMarkdown(RenderMarkdownIn) returns (String); } service DeckConfigService { @@ -236,32 +239,28 @@ service MediaService { rpc RestoreTrash(Empty) returns (Empty); } -service BackendService { - rpc LatestProgress(Empty) returns (Progress); - rpc SetWantsAbort(Empty) returns (Empty); - - // cards - - rpc GetCard(CardID) returns (Card); - rpc UpdateCard(UpdateCardIn) returns (Empty); - rpc RemoveCards(RemoveCardsIn) returns (Empty); - rpc SetDeck(SetDeckIn) returns (Empty); - - // collection +service I18nService { + rpc TranslateString(TranslateStringIn) returns (String); + rpc FormatTimespan(FormatTimespanIn) returns (String); + rpc I18nResources(Empty) returns (Json); +} +service CollectionService { rpc OpenCollection(OpenCollectionIn) returns (Empty); rpc CloseCollection(CloseCollectionIn) returns (Empty); rpc CheckDatabase(Empty) returns (CheckDatabaseOut); rpc GetUndoStatus(Empty) returns (UndoStatus); rpc Undo(Empty) returns (UndoStatus); rpc Redo(Empty) returns (UndoStatus); + rpc LatestProgress(Empty) returns (Progress); + rpc SetWantsAbort(Empty) returns (Empty); +} - // translation/messages/text manipulation - - rpc TranslateString(TranslateStringIn) returns (String); - rpc FormatTimespan(FormatTimespanIn) returns (String); - rpc I18nResources(Empty) returns (Json); - rpc RenderMarkdown(RenderMarkdownIn) returns (String); +service CardsService { + rpc GetCard(CardID) returns (Card); + rpc UpdateCard(UpdateCardIn) returns (Empty); + rpc RemoveCards(RemoveCardsIn) returns (Empty); + rpc SetDeck(SetDeckIn) returns (Empty); } // Protobuf stored in .anki2 files diff --git a/rslib/build/protobuf.rs b/rslib/build/protobuf.rs index 65c1c75ff..7ba44254d 100644 --- a/rslib/build/protobuf.rs +++ b/rslib/build/protobuf.rs @@ -21,7 +21,7 @@ pub trait Service { "{idx} => {{ let input = {input_type}::decode(input)?;\n", "let output = self.{rust_method}(input)?;\n", "let mut out_bytes = Vec::new(); output.encode(&mut out_bytes)?; Ok(out_bytes) }}, "), - idx = idx + 1, + idx = idx, input_type = method.input_type, rust_method = method.name ) diff --git a/rslib/src/backend/card.rs b/rslib/src/backend/card.rs index c859d8312..8172f3f56 100644 --- a/rslib/src/backend/card.rs +++ b/rslib/src/backend/card.rs @@ -1,13 +1,60 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; +use super::Backend; use crate::prelude::*; use crate::{ backend_proto as pb, card::{CardQueue, CardType}, }; +pub(super) use pb::cards_service::Service as CardsService; + +impl CardsService for Backend { + fn get_card(&self, input: pb::CardId) -> Result { + self.with_col(|col| { + col.storage + .get_card(input.into()) + .and_then(|opt| opt.ok_or(AnkiError::NotFound)) + .map(Into::into) + }) + } + + fn update_card(&self, input: pb::UpdateCardIn) -> Result { + self.with_col(|col| { + let op = if input.skip_undo_entry { + None + } else { + Some(UndoableOpKind::UpdateCard) + }; + let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?; + col.update_card_with_op(&mut card, op) + }) + .map(Into::into) + } + + fn remove_cards(&self, input: pb::RemoveCardsIn) -> Result { + self.with_col(|col| { + col.transact(None, |col| { + col.remove_cards_and_orphaned_notes( + &input + .card_ids + .into_iter() + .map(Into::into) + .collect::>(), + )?; + Ok(().into()) + }) + }) + } + + fn set_deck(&self, input: pb::SetDeckIn) -> Result { + 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)) + } +} impl TryFrom for Card { type Error = AnkiError; @@ -39,3 +86,28 @@ impl TryFrom for Card { }) } } + +impl From for pb::Card { + fn from(c: Card) -> Self { + pb::Card { + id: c.id.0, + note_id: c.note_id.0, + deck_id: c.deck_id.0, + template_idx: c.template_idx as u32, + mtime_secs: c.mtime.0, + usn: c.usn.0, + ctype: c.ctype as u32, + queue: c.queue as i32, + due: c.due, + interval: c.interval, + ease_factor: c.ease_factor as u32, + reps: c.reps, + lapses: c.lapses, + remaining_steps: c.remaining_steps, + original_due: c.original_due, + original_deck_id: c.original_deck_id.0, + flags: c.flags as u32, + data: c.data, + } + } +} diff --git a/rslib/src/backend/cardrendering.rs b/rslib/src/backend/cardrendering.rs index 9bbe8b805..a245973af 100644 --- a/rslib/src/backend/cardrendering.rs +++ b/rslib/src/backend/cardrendering.rs @@ -5,9 +5,11 @@ use super::Backend; use crate::{ backend_proto as pb, latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex}, - notetype::CardTemplateSchema11, + markdown::render_markdown, + notetype::{CardTemplateSchema11, RenderCardOutput}, prelude::*, - text::{extract_av_tags, strip_av_tags, AVTag}, + template::RenderedNode, + text::{extract_av_tags, sanitize_html_no_images, strip_av_tags, AVTag}, }; pub(super) use pb::cardrendering_service::Service as CardRenderingService; @@ -116,4 +118,46 @@ impl CardRenderingService for Backend { val: strip_av_tags(&input.val).into(), }) } + + fn render_markdown(&self, input: pb::RenderMarkdownIn) -> Result { + let mut text = render_markdown(&input.markdown); + if input.sanitize { + // currently no images + text = sanitize_html_no_images(&text); + } + Ok(text.into()) + } +} + +fn rendered_nodes_to_proto(nodes: Vec) -> Vec { + nodes + .into_iter() + .map(|n| pb::RenderedTemplateNode { + value: Some(rendered_node_to_proto(n)), + }) + .collect() +} + +fn rendered_node_to_proto(node: RenderedNode) -> pb::rendered_template_node::Value { + match node { + RenderedNode::Text { text } => pb::rendered_template_node::Value::Text(text), + RenderedNode::Replacement { + field_name, + current_text, + filters, + } => pb::rendered_template_node::Value::Replacement(pb::RenderedTemplateReplacement { + field_name, + current_text, + filters, + }), + } +} + +impl From for pb::RenderCardOut { + fn from(o: RenderCardOutput) -> Self { + pb::RenderCardOut { + question_nodes: rendered_nodes_to_proto(o.qnodes), + answer_nodes: rendered_nodes_to_proto(o.anodes), + } + } } diff --git a/rslib/src/backend/collection.rs b/rslib/src/backend/collection.rs new file mode 100644 index 000000000..6153d1f9d --- /dev/null +++ b/rslib/src/backend/collection.rs @@ -0,0 +1,104 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::{progress::Progress, Backend}; +use crate::{ + backend::progress::progress_to_proto, + backend_proto as pb, + collection::open_collection, + log::{self, default_logger}, + prelude::*, +}; +pub(super) use pb::collection_service::Service as CollectionService; +use slog::error; + +impl CollectionService for Backend { + fn latest_progress(&self, _input: pb::Empty) -> Result { + let progress = self.progress_state.lock().unwrap().last_progress; + Ok(progress_to_proto(progress, &self.i18n)) + } + + fn set_wants_abort(&self, _input: pb::Empty) -> Result { + self.progress_state.lock().unwrap().want_abort = true; + Ok(().into()) + } + + fn open_collection(&self, input: pb::OpenCollectionIn) -> Result { + let mut col = self.col.lock().unwrap(); + if col.is_some() { + return Err(AnkiError::CollectionAlreadyOpen); + } + + let mut path = input.collection_path.clone(); + path.push_str(".log"); + + let log_path = match input.log_path.as_str() { + "" => None, + path => Some(path), + }; + let logger = default_logger(log_path)?; + + let new_col = open_collection( + input.collection_path, + input.media_folder_path, + input.media_db_path, + self.server, + self.i18n.clone(), + logger, + )?; + + *col = Some(new_col); + + Ok(().into()) + } + + fn close_collection(&self, input: pb::CloseCollectionIn) -> Result { + self.abort_media_sync_and_wait(); + + let mut col = self.col.lock().unwrap(); + if col.is_none() { + return Err(AnkiError::CollectionNotOpen); + } + + let col_inner = col.take().unwrap(); + if input.downgrade_to_schema11 { + let log = log::terminal(); + if let Err(e) = col_inner.close(input.downgrade_to_schema11) { + error!(log, " failed: {:?}", e); + } + } + + Ok(().into()) + } + + fn check_database(&self, _input: pb::Empty) -> Result { + let mut handler = self.new_progress_handler(); + let progress_fn = move |progress, throttle| { + handler.update(Progress::DatabaseCheck(progress), throttle); + }; + self.with_col(|col| { + col.check_database(progress_fn) + .map(|problems| pb::CheckDatabaseOut { + problems: problems.to_i18n_strings(&col.i18n), + }) + }) + } + + fn get_undo_status(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.undo_status())) + } + + fn undo(&self, _input: pb::Empty) -> Result { + self.with_col(|col| { + col.undo()?; + Ok(col.undo_status()) + }) + } + + fn redo(&self, _input: pb::Empty) -> Result { + self.with_col(|col| { + col.redo()?; + Ok(col.undo_status()) + }) + } +} diff --git a/rslib/src/backend/i18n.rs b/rslib/src/backend/i18n.rs new file mode 100644 index 000000000..6a17a517e --- /dev/null +++ b/rslib/src/backend/i18n.rs @@ -0,0 +1,55 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::Backend; +use crate::{ + backend_proto as pb, + prelude::*, + scheduler::timespan::{answer_button_time, time_span}, +}; +use fluent::FluentValue; +pub(super) use pb::i18n_service::Service as I18nService; + +impl I18nService for Backend { + fn translate_string(&self, input: pb::TranslateStringIn) -> Result { + let key = match crate::fluent_proto::FluentString::from_i32(input.key) { + Some(key) => key, + None => return Ok("invalid key".to_string().into()), + }; + + let map = input + .args + .iter() + .map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v))) + .collect(); + + Ok(self.i18n.trn(key, map).into()) + } + + fn format_timespan(&self, input: pb::FormatTimespanIn) -> Result { + use pb::format_timespan_in::Context; + Ok(match input.context() { + Context::Precise => time_span(input.seconds, &self.i18n, true), + Context::Intervals => time_span(input.seconds, &self.i18n, false), + Context::AnswerButtons => answer_button_time(input.seconds, &self.i18n), + } + .into()) + } + + fn i18n_resources(&self, _input: pb::Empty) -> Result { + serde_json::to_vec(&self.i18n.resources_for_js()) + .map(Into::into) + .map_err(Into::into) + } +} + +fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { + use pb::translate_arg_value::Value as V; + match &arg.value { + Some(val) => match val { + V::Str(s) => FluentValue::String(s.into()), + V::Number(f) => FluentValue::Number(f.into()), + }, + None => FluentValue::String("".into()), + } +} diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index bd48cb2bb..a13cb88f9 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -4,12 +4,14 @@ mod adding; mod card; mod cardrendering; +mod collection; mod config; mod dbproxy; mod deckconfig; mod decks; mod err; mod generic; +mod i18n; mod media; mod notes; mod notetypes; @@ -21,54 +23,42 @@ mod sync; mod tags; use self::{ + card::CardsService, cardrendering::CardRenderingService, + collection::CollectionService, config::ConfigService, deckconfig::DeckConfigService, decks::DecksService, + i18n::I18nService, media::MediaService, notes::NotesService, notetypes::NoteTypesService, + progress::ProgressState, scheduler::SchedulingService, search::SearchService, stats::StatsService, sync::{SyncService, SyncState}, tags::TagsService, }; -use crate::backend_proto::backend_service::Service as BackendService; use crate::{ backend::dbproxy::db_command_bytes, backend_proto as pb, - backend_proto::RenderedTemplateReplacement, - card::{Card, CardID}, - collection::{open_collection, Collection}, + collection::Collection, err::{AnkiError, Result}, i18n::I18n, log, - log::default_logger, - markdown::render_markdown, - notetype::RenderCardOutput, - scheduler::timespan::{answer_button_time, time_span}, - template::RenderedNode, - text::sanitize_html_no_images, - undo::UndoableOpKind, }; -use fluent::FluentValue; -use log::error; use once_cell::sync::OnceCell; -use progress::{AbortHandleSlot, Progress}; +use progress::AbortHandleSlot; use prost::Message; -use std::convert::TryInto; use std::{ result, sync::{Arc, Mutex}, }; use tokio::runtime::{self, Runtime}; -use self::{ - err::anki_error_to_proto_error, - progress::{progress_to_proto, ProgressState}, -}; +use self::err::anki_error_to_proto_error; pub struct Backend { col: Arc>>, @@ -80,8 +70,6 @@ pub struct Backend { state: Arc>, } -// fixme: move other items like runtime into here as well - #[derive(Default)] struct BackendState { sync: SyncState, @@ -102,189 +90,6 @@ pub fn init_backend(init_msg: &[u8]) -> std::result::Result { Ok(Backend::new(i18n, input.server)) } -impl BackendService for Backend { - fn latest_progress(&self, _input: pb::Empty) -> Result { - let progress = self.progress_state.lock().unwrap().last_progress; - Ok(progress_to_proto(progress, &self.i18n)) - } - - fn set_wants_abort(&self, _input: pb::Empty) -> Result { - self.progress_state.lock().unwrap().want_abort = true; - Ok(().into()) - } - - // cards - //------------------------------------------------------------------- - - fn get_card(&self, input: pb::CardId) -> Result { - self.with_col(|col| { - col.storage - .get_card(input.into()) - .and_then(|opt| opt.ok_or(AnkiError::NotFound)) - .map(Into::into) - }) - } - - fn update_card(&self, input: pb::UpdateCardIn) -> Result { - self.with_col(|col| { - let op = if input.skip_undo_entry { - None - } else { - Some(UndoableOpKind::UpdateCard) - }; - let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?; - col.update_card_with_op(&mut card, op) - }) - .map(Into::into) - } - - fn remove_cards(&self, input: pb::RemoveCardsIn) -> Result { - self.with_col(|col| { - col.transact(None, |col| { - col.remove_cards_and_orphaned_notes( - &input - .card_ids - .into_iter() - .map(Into::into) - .collect::>(), - )?; - Ok(().into()) - }) - }) - } - - fn set_deck(&self, input: pb::SetDeckIn) -> Result { - 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)) - } - - // collection - //------------------------------------------------------------------- - - fn open_collection(&self, input: pb::OpenCollectionIn) -> Result { - let mut col = self.col.lock().unwrap(); - if col.is_some() { - return Err(AnkiError::CollectionAlreadyOpen); - } - - let mut path = input.collection_path.clone(); - path.push_str(".log"); - - let log_path = match input.log_path.as_str() { - "" => None, - path => Some(path), - }; - let logger = default_logger(log_path)?; - - let new_col = open_collection( - input.collection_path, - input.media_folder_path, - input.media_db_path, - self.server, - self.i18n.clone(), - logger, - )?; - - *col = Some(new_col); - - Ok(().into()) - } - - fn close_collection(&self, input: pb::CloseCollectionIn) -> Result { - self.abort_media_sync_and_wait(); - - let mut col = self.col.lock().unwrap(); - if col.is_none() { - return Err(AnkiError::CollectionNotOpen); - } - - let col_inner = col.take().unwrap(); - if input.downgrade_to_schema11 { - let log = log::terminal(); - if let Err(e) = col_inner.close(input.downgrade_to_schema11) { - error!(log, " failed: {:?}", e); - } - } - - Ok(().into()) - } - - fn check_database(&self, _input: pb::Empty) -> Result { - let mut handler = self.new_progress_handler(); - let progress_fn = move |progress, throttle| { - handler.update(Progress::DatabaseCheck(progress), throttle); - }; - self.with_col(|col| { - col.check_database(progress_fn) - .map(|problems| pb::CheckDatabaseOut { - problems: problems.to_i18n_strings(&col.i18n), - }) - }) - } - - fn get_undo_status(&self, _input: pb::Empty) -> Result { - self.with_col(|col| Ok(col.undo_status())) - } - - fn undo(&self, _input: pb::Empty) -> Result { - self.with_col(|col| { - col.undo()?; - Ok(col.undo_status()) - }) - } - - fn redo(&self, _input: pb::Empty) -> Result { - self.with_col(|col| { - col.redo()?; - Ok(col.undo_status()) - }) - } - - // i18n/messages - //------------------------------------------------------------------- - - fn translate_string(&self, input: pb::TranslateStringIn) -> Result { - let key = match crate::fluent_proto::FluentString::from_i32(input.key) { - Some(key) => key, - None => return Ok("invalid key".to_string().into()), - }; - - let map = input - .args - .iter() - .map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v))) - .collect(); - - Ok(self.i18n.trn(key, map).into()) - } - - fn format_timespan(&self, input: pb::FormatTimespanIn) -> Result { - use pb::format_timespan_in::Context; - Ok(match input.context() { - Context::Precise => time_span(input.seconds, &self.i18n, true), - Context::Intervals => time_span(input.seconds, &self.i18n, false), - Context::AnswerButtons => answer_button_time(input.seconds, &self.i18n), - } - .into()) - } - - fn i18n_resources(&self, _input: pb::Empty) -> Result { - serde_json::to_vec(&self.i18n.resources_for_js()) - .map(Into::into) - .map_err(Into::into) - } - - fn render_markdown(&self, input: pb::RenderMarkdownIn) -> Result { - let mut text = render_markdown(&input.markdown); - if input.sanitize { - // currently no images - text = sanitize_html_no_images(&text); - } - Ok(text.into()) - } -} - impl Backend { pub fn new(i18n: I18n, server: bool) -> Backend { Backend { @@ -315,7 +120,6 @@ impl Backend { .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), pb::ServiceIndex::Decks => DecksService::run_method(self, method, input), pb::ServiceIndex::Notes => NotesService::run_method(self, method, input), pb::ServiceIndex::NoteTypes => NoteTypesService::run_method(self, method, input), @@ -329,6 +133,9 @@ impl Backend { pb::ServiceIndex::Media => MediaService::run_method(self, method, input), pb::ServiceIndex::Stats => StatsService::run_method(self, method, input), pb::ServiceIndex::Search => SearchService::run_method(self, method, input), + pb::ServiceIndex::I18n => I18nService::run_method(self, method, input), + pb::ServiceIndex::Collection => CollectionService::run_method(self, method, input), + pb::ServiceIndex::Cards => CardsService::run_method(self, method, input), }) .map_err(|err| { let backend_err = anki_error_to_proto_error(err, &self.i18n); @@ -338,6 +145,15 @@ impl Backend { }) } + pub fn run_db_command_bytes(&self, input: &[u8]) -> std::result::Result, Vec> { + self.db_command(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(); + bytes + }) + } + /// If collection is open, run the provided closure while holding /// the mutex. /// If collection is not open, return an error. @@ -368,94 +184,7 @@ impl Backend { .clone() } - pub fn db_command(&self, input: &[u8]) -> Result> { + fn db_command(&self, input: &[u8]) -> Result> { self.with_col(|col| db_command_bytes(col, input)) } - - pub fn run_db_command_bytes(&self, input: &[u8]) -> std::result::Result, Vec> { - self.db_command(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(); - bytes - }) - } -} - -fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { - use pb::translate_arg_value::Value as V; - match &arg.value { - Some(val) => match val { - V::Str(s) => FluentValue::String(s.into()), - V::Number(f) => FluentValue::Number(f.into()), - }, - None => FluentValue::String("".into()), - } -} - -fn rendered_nodes_to_proto(nodes: Vec) -> Vec { - nodes - .into_iter() - .map(|n| pb::RenderedTemplateNode { - value: Some(rendered_node_to_proto(n)), - }) - .collect() -} - -fn rendered_node_to_proto(node: RenderedNode) -> pb::rendered_template_node::Value { - match node { - RenderedNode::Text { text } => pb::rendered_template_node::Value::Text(text), - RenderedNode::Replacement { - field_name, - current_text, - filters, - } => pb::rendered_template_node::Value::Replacement(RenderedTemplateReplacement { - field_name, - current_text, - filters, - }), - } -} - -impl From for pb::RenderCardOut { - fn from(o: RenderCardOutput) -> Self { - pb::RenderCardOut { - question_nodes: rendered_nodes_to_proto(o.qnodes), - answer_nodes: rendered_nodes_to_proto(o.anodes), - } - } -} - -impl From for pb::Card { - fn from(c: Card) -> Self { - pb::Card { - id: c.id.0, - note_id: c.note_id.0, - deck_id: c.deck_id.0, - template_idx: c.template_idx as u32, - mtime_secs: c.mtime.0, - usn: c.usn.0, - ctype: c.ctype as u32, - queue: c.queue as i32, - due: c.due, - interval: c.interval, - ease_factor: c.ease_factor as u32, - reps: c.reps, - lapses: c.lapses, - remaining_steps: c.remaining_steps, - original_due: c.original_due, - original_deck_id: c.original_deck_id.0, - flags: c.flags as u32, - data: c.data, - } - } -} - -impl From for pb::SchedTimingTodayOut { - fn from(t: crate::scheduler::timing::SchedTimingToday) -> pb::SchedTimingTodayOut { - pb::SchedTimingTodayOut { - days_elapsed: t.days_elapsed, - next_day_at: t.next_day_at, - } - } } diff --git a/rslib/src/backend/scheduler/mod.rs b/rslib/src/backend/scheduler/mod.rs index 1e8a0a19c..63ffce7e5 100644 --- a/rslib/src/backend/scheduler/mod.rs +++ b/rslib/src/backend/scheduler/mod.rs @@ -175,3 +175,12 @@ impl SchedulingService for Backend { self.with_col(|col| col.get_queued_cards(input.fetch_limit, input.intraday_learning_only)) } } + +impl From for pb::SchedTimingTodayOut { + fn from(t: crate::scheduler::timing::SchedTimingToday) -> pb::SchedTimingTodayOut { + pb::SchedTimingTodayOut { + days_elapsed: t.days_elapsed, + next_day_at: t.next_day_at, + } + } +}