mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
split out remaining rpc methods
@david-allison-1 note this also changes the method index to start at 0 instead of 1
This commit is contained in:
parent
378b116977
commit
4bd120cc4b
9 changed files with 331 additions and 319 deletions
|
@ -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}
|
||||
"""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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<pb::Card> {
|
||||
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<pb::Empty> {
|
||||
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<pb::Empty> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
col.remove_cards_and_orphaned_notes(
|
||||
&input
|
||||
.card_ids
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
Ok(().into())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn set_deck(&self, input: pb::SetDeckIn) -> Result<pb::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))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<pb::Card> for Card {
|
||||
type Error = AnkiError;
|
||||
|
@ -39,3 +86,28 @@ impl TryFrom<pb::Card> for Card {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Card> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<pb::String> {
|
||||
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<RenderedNode>) -> Vec<pb::RenderedTemplateNode> {
|
||||
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<RenderCardOutput> 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
104
rslib/src/backend/collection.rs
Normal file
104
rslib/src/backend/collection.rs
Normal file
|
@ -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<pb::Progress> {
|
||||
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<pb::Empty> {
|
||||
self.progress_state.lock().unwrap().want_abort = true;
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<pb::Empty> {
|
||||
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<pb::Empty> {
|
||||
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<pb::CheckDatabaseOut> {
|
||||
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<pb::UndoStatus> {
|
||||
self.with_col(|col| Ok(col.undo_status()))
|
||||
}
|
||||
|
||||
fn undo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
|
||||
self.with_col(|col| {
|
||||
col.undo()?;
|
||||
Ok(col.undo_status())
|
||||
})
|
||||
}
|
||||
|
||||
fn redo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
|
||||
self.with_col(|col| {
|
||||
col.redo()?;
|
||||
Ok(col.undo_status())
|
||||
})
|
||||
}
|
||||
}
|
55
rslib/src/backend/i18n.rs
Normal file
55
rslib/src/backend/i18n.rs
Normal file
|
@ -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<pb::String> {
|
||||
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<pb::String> {
|
||||
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<pb::Json> {
|
||||
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()),
|
||||
}
|
||||
}
|
|
@ -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<Mutex<Option<Collection>>>,
|
||||
|
@ -80,8 +70,6 @@ pub struct Backend {
|
|||
state: Arc<Mutex<BackendState>>,
|
||||
}
|
||||
|
||||
// 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<Backend, String> {
|
|||
Ok(Backend::new(i18n, input.server))
|
||||
}
|
||||
|
||||
impl BackendService for Backend {
|
||||
fn latest_progress(&self, _input: pb::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: pb::Empty) -> Result<pb::Empty> {
|
||||
self.progress_state.lock().unwrap().want_abort = true;
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
// cards
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn get_card(&self, input: pb::CardId) -> Result<pb::Card> {
|
||||
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<pb::Empty> {
|
||||
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<pb::Empty> {
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
col.remove_cards_and_orphaned_notes(
|
||||
&input
|
||||
.card_ids
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
Ok(().into())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn set_deck(&self, input: pb::SetDeckIn) -> Result<pb::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))
|
||||
}
|
||||
|
||||
// collection
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<pb::Empty> {
|
||||
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<pb::Empty> {
|
||||
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<pb::CheckDatabaseOut> {
|
||||
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<pb::UndoStatus> {
|
||||
self.with_col(|col| Ok(col.undo_status()))
|
||||
}
|
||||
|
||||
fn undo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
|
||||
self.with_col(|col| {
|
||||
col.undo()?;
|
||||
Ok(col.undo_status())
|
||||
})
|
||||
}
|
||||
|
||||
fn redo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
|
||||
self.with_col(|col| {
|
||||
col.redo()?;
|
||||
Ok(col.undo_status())
|
||||
})
|
||||
}
|
||||
|
||||
// i18n/messages
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
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()),
|
||||
};
|
||||
|
||||
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<pb::String> {
|
||||
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<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) -> Result<pb::String> {
|
||||
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<u8>, Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
fn db_command(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||
self.with_col(|col| db_command_bytes(col, input))
|
||||
}
|
||||
|
||||
pub fn run_db_command_bytes(&self, input: &[u8]) -> std::result::Result<Vec<u8>, Vec<u8>> {
|
||||
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<RenderedNode>) -> Vec<pb::RenderedTemplateNode> {
|
||||
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<RenderCardOutput> 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<Card> 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<crate::scheduler::timing::SchedTimingToday> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<crate::scheduler::timing::SchedTimingToday> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue