From cd14987812422a26dcd8c0d12fe0dc788a4bf858 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 11 Mar 2021 16:05:06 +1000 Subject: [PATCH] split out tags, deck config and card rendering --- pylib/anki/_backend/genbackend.py | 4 +- rslib/backend.proto | 53 +++---- rslib/src/backend/cardrendering.rs | 119 +++++++++++++++ rslib/src/backend/deckconfig.rs | 60 ++++++++ rslib/src/backend/mod.rs | 232 ++--------------------------- rslib/src/backend/tags.rs | 58 ++++++++ 6 files changed, 283 insertions(+), 243 deletions(-) create mode 100644 rslib/src/backend/cardrendering.rs create mode 100644 rslib/src/backend/deckconfig.rs create mode 100644 rslib/src/backend/tags.rs diff --git a/pylib/anki/_backend/genbackend.py b/pylib/anki/_backend/genbackend.py index 31c44ee86..633fe1f88 100755 --- a/pylib/anki/_backend/genbackend.py +++ b/pylib/anki/_backend/genbackend.py @@ -160,7 +160,9 @@ def render_service( for service in pb.ServiceIndex.DESCRIPTOR.values: # SERVICE_INDEX_TEST -> _TESTSERVICE - service_var = service.name.replace("SERVICE_INDEX", "") + "SERVICE" + service_var = ( + "_" + service.name.replace("SERVICE_INDEX", "").replace("_", "") + "SERVICE" + ) service_obj = getattr(pb, service_var) service_index = service.number render_service(service_obj, service_index) diff --git a/rslib/backend.proto b/rslib/backend.proto index a7b02f105..9ed4e824d 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -83,8 +83,11 @@ enum ServiceIndex { SERVICE_INDEX_DECKS = 1; SERVICE_INDEX_NOTES = 2; SERVICE_INDEX_SYNC = 3; - SERVICE_INDEX_NOTETYPES = 4; + SERVICE_INDEX_NOTE_TYPES = 4; SERVICE_INDEX_CONFIG = 5; + SERVICE_INDEX_CARD_RENDERING = 6; + SERVICE_INDEX_DECK_CONFIG = 7; + SERVICE_INDEX_TAGS = 8; SERVICE_INDEX_BACKEND = 99; } @@ -179,18 +182,36 @@ service NoteTypesService { rpc RemoveNotetype(NoteTypeID) returns (Empty); } -service BackendService { - rpc LatestProgress(Empty) returns (Progress); - rpc SetWantsAbort(Empty) returns (Empty); - - // card rendering - +service CardRenderingService { 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); +} + +service DeckConfigService { + rpc AddOrUpdateDeckConfigLegacy(AddOrUpdateDeckConfigLegacyIn) + returns (DeckConfigID); + rpc AllDeckConfigLegacy(Empty) returns (Json); + rpc GetDeckConfigLegacy(DeckConfigID) returns (Json); + rpc NewDeckConfigLegacy(Empty) returns (Json); + rpc RemoveDeckConfig(DeckConfigID) returns (Empty); +} + +service TagsService { + rpc ClearUnusedTags(Empty) returns (Empty); + rpc AllTags(Empty) returns (StringList); + rpc SetTagExpanded(SetTagExpandedIn) returns (Empty); + rpc ClearTag(String) returns (Empty); + rpc TagTree(Empty) returns (TagTreeNode); + rpc DragDropTags(DragDropTagsIn) returns (Empty); +} + +service BackendService { + rpc LatestProgress(Empty) returns (Progress); + rpc SetWantsAbort(Empty) returns (Empty); // searching @@ -216,15 +237,6 @@ service BackendService { rpc EmptyTrash(Empty) returns (Empty); rpc RestoreTrash(Empty) returns (Empty); - // deck config - - rpc AddOrUpdateDeckConfigLegacy(AddOrUpdateDeckConfigLegacyIn) - returns (DeckConfigID); - rpc AllDeckConfigLegacy(Empty) returns (Json); - rpc GetDeckConfigLegacy(DeckConfigID) returns (Json); - rpc NewDeckConfigLegacy(Empty) returns (Json); - rpc RemoveDeckConfig(DeckConfigID) returns (Empty); - // cards rpc GetCard(CardID) returns (Card); @@ -247,15 +259,6 @@ service BackendService { rpc FormatTimespan(FormatTimespanIn) returns (String); rpc I18nResources(Empty) returns (Json); rpc RenderMarkdown(RenderMarkdownIn) returns (String); - - // tags - - rpc ClearUnusedTags(Empty) returns (Empty); - rpc AllTags(Empty) returns (StringList); - rpc SetTagExpanded(SetTagExpandedIn) returns (Empty); - rpc ClearTag(String) returns (Empty); - rpc TagTree(Empty) returns (TagTreeNode); - rpc DragDropTags(DragDropTagsIn) returns (Empty); } // Protobuf stored in .anki2 files diff --git a/rslib/src/backend/cardrendering.rs b/rslib/src/backend/cardrendering.rs new file mode 100644 index 000000000..9bbe8b805 --- /dev/null +++ b/rslib/src/backend/cardrendering.rs @@ -0,0 +1,119 @@ +// 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, + latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex}, + notetype::CardTemplateSchema11, + prelude::*, + text::{extract_av_tags, strip_av_tags, AVTag}, +}; +pub(super) use pb::cardrendering_service::Service as CardRenderingService; + +impl CardRenderingService for Backend { + fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> Result { + let (text, tags) = extract_av_tags(&input.text, input.question_side); + let pt_tags = tags + .into_iter() + .map(|avtag| match avtag { + AVTag::SoundOrVideo(file) => pb::AvTag { + value: Some(pb::av_tag::Value::SoundOrVideo(file)), + }, + AVTag::TextToSpeech { + field_text, + lang, + voices, + other_args, + speed, + } => pb::AvTag { + value: Some(pb::av_tag::Value::Tts(pb::TtsTag { + field_text, + lang, + voices, + other_args, + speed, + })), + }, + }) + .collect(); + + Ok(pb::ExtractAvTagsOut { + text: text.into(), + av_tags: pt_tags, + }) + } + + fn extract_latex(&self, input: pb::ExtractLatexIn) -> Result { + let func = if input.expand_clozes { + extract_latex_expanding_clozes + } else { + extract_latex + }; + let (text, extracted) = func(&input.text, input.svg); + + Ok(pb::ExtractLatexOut { + text, + latex: extracted + .into_iter() + .map(|e: ExtractedLatex| pb::ExtractedLatex { + filename: e.fname, + latex_body: e.latex, + }) + .collect(), + }) + } + + fn get_empty_cards(&self, _input: pb::Empty) -> Result { + self.with_col(|col| { + let mut empty = col.empty_cards()?; + let report = col.empty_cards_report(&mut empty)?; + + let mut outnotes = vec![]; + for (_ntid, notes) in empty { + outnotes.extend(notes.into_iter().map(|e| { + pb::empty_cards_report::NoteWithEmptyCards { + note_id: e.nid.0, + will_delete_note: e.empty.len() == e.current_count, + card_ids: e.empty.into_iter().map(|(_ord, id)| id.0).collect(), + } + })) + } + Ok(pb::EmptyCardsReport { + report, + notes: outnotes, + }) + }) + } + + fn render_existing_card(&self, input: pb::RenderExistingCardIn) -> Result { + self.with_col(|col| { + col.render_existing_card(CardID(input.card_id), input.browser) + .map(Into::into) + }) + } + + fn render_uncommitted_card( + &self, + input: pb::RenderUncommittedCardIn, + ) -> Result { + let schema11: CardTemplateSchema11 = serde_json::from_slice(&input.template)?; + let template = schema11.into(); + let mut note = input + .note + .ok_or_else(|| AnkiError::invalid_input("missing note"))? + .into(); + let ord = input.card_ord as u16; + let fill_empty = input.fill_empty; + self.with_col(|col| { + col.render_uncommitted_card(&mut note, &template, ord, fill_empty) + .map(Into::into) + }) + } + + fn strip_av_tags(&self, input: pb::String) -> Result { + Ok(pb::String { + val: strip_av_tags(&input.val).into(), + }) + } +} diff --git a/rslib/src/backend/deckconfig.rs b/rslib/src/backend/deckconfig.rs new file mode 100644 index 000000000..9b20fe8f7 --- /dev/null +++ b/rslib/src/backend/deckconfig.rs @@ -0,0 +1,60 @@ +// 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, + deckconf::{DeckConf, DeckConfSchema11}, + prelude::*, +}; +pub(super) use pb::deckconfig_service::Service as DeckConfigService; + +impl DeckConfigService for Backend { + fn add_or_update_deck_config_legacy( + &self, + input: pb::AddOrUpdateDeckConfigLegacyIn, + ) -> Result { + let conf: DeckConfSchema11 = serde_json::from_slice(&input.config)?; + let mut conf: DeckConf = conf.into(); + self.with_col(|col| { + col.transact(None, |col| { + col.add_or_update_deck_config(&mut conf, input.preserve_usn_and_mtime)?; + Ok(pb::DeckConfigId { dcid: conf.id.0 }) + }) + }) + .map(Into::into) + } + + fn all_deck_config_legacy(&self, _input: pb::Empty) -> Result { + self.with_col(|col| { + let conf: Vec = col + .storage + .all_deck_config()? + .into_iter() + .map(Into::into) + .collect(); + serde_json::to_vec(&conf).map_err(Into::into) + }) + .map(Into::into) + } + + fn get_deck_config_legacy(&self, input: pb::DeckConfigId) -> Result { + self.with_col(|col| { + let conf = col.get_deck_config(input.into(), true)?.unwrap(); + let conf: DeckConfSchema11 = conf.into(); + Ok(serde_json::to_vec(&conf)?) + }) + .map(Into::into) + } + + fn new_deck_config_legacy(&self, _input: pb::Empty) -> Result { + serde_json::to_vec(&DeckConfSchema11::default()) + .map_err(Into::into) + .map(Into::into) + } + + fn remove_deck_config(&self, input: pb::DeckConfigId) -> Result { + self.with_col(|col| col.transact(None, |col| col.remove_deck_config(input.into()))) + .map(Into::into) + } +} diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index ecc65e4f7..91b591662 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -3,8 +3,10 @@ mod adding; mod card; +mod cardrendering; mod config; mod dbproxy; +mod deckconfig; mod decks; mod err; mod generic; @@ -14,38 +16,40 @@ mod progress; mod scheduler; mod search; mod sync; +mod tags; use self::{ + cardrendering::CardRenderingService, config::ConfigService, + deckconfig::DeckConfigService, decks::DecksService, notes::NotesService, notetypes::NoteTypesService, scheduler::SchedulingService, 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::{AddOrUpdateDeckConfigLegacyIn, RenderedTemplateReplacement}, + backend_proto::RenderedTemplateReplacement, card::{Card, CardID}, collection::{open_collection, Collection}, - deckconf::{DeckConf, DeckConfSchema11}, err::{AnkiError, Result}, i18n::I18n, - latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex}, log, log::default_logger, markdown::render_markdown, media::check::MediaChecker, media::MediaManager, notes::NoteID, - notetype::{CardTemplateSchema11, RenderCardOutput}, + notetype::RenderCardOutput, scheduler::timespan::{answer_button_time, time_span}, search::{concatenate_searches, replace_search_node, write_nodes, Node}, template::RenderedNode, - text::{extract_av_tags, sanitize_html_no_images, strip_av_tags, AVTag}, + text::sanitize_html_no_images, undo::UndoableOpKind, }; use fluent::FluentValue; @@ -108,113 +112,6 @@ impl BackendService for Backend { Ok(().into()) } - // card rendering - - fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> Result { - let (text, tags) = extract_av_tags(&input.text, input.question_side); - let pt_tags = tags - .into_iter() - .map(|avtag| match avtag { - AVTag::SoundOrVideo(file) => pb::AvTag { - value: Some(pb::av_tag::Value::SoundOrVideo(file)), - }, - AVTag::TextToSpeech { - field_text, - lang, - voices, - other_args, - speed, - } => pb::AvTag { - value: Some(pb::av_tag::Value::Tts(pb::TtsTag { - field_text, - lang, - voices, - other_args, - speed, - })), - }, - }) - .collect(); - - Ok(pb::ExtractAvTagsOut { - text: text.into(), - av_tags: pt_tags, - }) - } - - fn extract_latex(&self, input: pb::ExtractLatexIn) -> Result { - let func = if input.expand_clozes { - extract_latex_expanding_clozes - } else { - extract_latex - }; - let (text, extracted) = func(&input.text, input.svg); - - Ok(pb::ExtractLatexOut { - text, - latex: extracted - .into_iter() - .map(|e: ExtractedLatex| pb::ExtractedLatex { - filename: e.fname, - latex_body: e.latex, - }) - .collect(), - }) - } - - fn get_empty_cards(&self, _input: pb::Empty) -> Result { - self.with_col(|col| { - let mut empty = col.empty_cards()?; - let report = col.empty_cards_report(&mut empty)?; - - let mut outnotes = vec![]; - for (_ntid, notes) in empty { - outnotes.extend(notes.into_iter().map(|e| { - pb::empty_cards_report::NoteWithEmptyCards { - note_id: e.nid.0, - will_delete_note: e.empty.len() == e.current_count, - card_ids: e.empty.into_iter().map(|(_ord, id)| id.0).collect(), - } - })) - } - Ok(pb::EmptyCardsReport { - report, - notes: outnotes, - }) - }) - } - - fn render_existing_card(&self, input: pb::RenderExistingCardIn) -> Result { - self.with_col(|col| { - col.render_existing_card(CardID(input.card_id), input.browser) - .map(Into::into) - }) - } - - fn render_uncommitted_card( - &self, - input: pb::RenderUncommittedCardIn, - ) -> Result { - let schema11: CardTemplateSchema11 = serde_json::from_slice(&input.template)?; - let template = schema11.into(); - let mut note = input - .note - .ok_or_else(|| AnkiError::invalid_input("missing note"))? - .into(); - let ord = input.card_ord as u16; - let fill_empty = input.fill_empty; - self.with_col(|col| { - col.render_uncommitted_card(&mut note, &template, ord, fill_empty) - .map(Into::into) - }) - } - - fn strip_av_tags(&self, input: pb::String) -> Result { - Ok(pb::String { - val: strip_av_tags(&input.val).into(), - }) - } - // searching //----------------------------------------------- @@ -384,57 +281,6 @@ impl BackendService for Backend { .map(Into::into) } - // deck config - //---------------------------------------------------- - - fn add_or_update_deck_config_legacy( - &self, - input: AddOrUpdateDeckConfigLegacyIn, - ) -> Result { - let conf: DeckConfSchema11 = serde_json::from_slice(&input.config)?; - let mut conf: DeckConf = conf.into(); - self.with_col(|col| { - col.transact(None, |col| { - col.add_or_update_deck_config(&mut conf, input.preserve_usn_and_mtime)?; - Ok(pb::DeckConfigId { dcid: conf.id.0 }) - }) - }) - .map(Into::into) - } - - fn all_deck_config_legacy(&self, _input: pb::Empty) -> Result { - self.with_col(|col| { - let conf: Vec = col - .storage - .all_deck_config()? - .into_iter() - .map(Into::into) - .collect(); - serde_json::to_vec(&conf).map_err(Into::into) - }) - .map(Into::into) - } - - fn get_deck_config_legacy(&self, input: pb::DeckConfigId) -> Result { - self.with_col(|col| { - let conf = col.get_deck_config(input.into(), true)?.unwrap(); - let conf: DeckConfSchema11 = conf.into(); - Ok(serde_json::to_vec(&conf)?) - }) - .map(Into::into) - } - - fn new_deck_config_legacy(&self, _input: pb::Empty) -> Result { - serde_json::to_vec(&DeckConfSchema11::default()) - .map_err(Into::into) - .map(Into::into) - } - - fn remove_deck_config(&self, input: pb::DeckConfigId) -> Result { - self.with_col(|col| col.transact(None, |col| col.remove_deck_config(input.into()))) - .map(Into::into) - } - // cards //------------------------------------------------------------------- @@ -605,59 +451,6 @@ impl BackendService for Backend { } Ok(text.into()) } - - // tags - //------------------------------------------------------------------- - - fn clear_unused_tags(&self, _input: pb::Empty) -> Result { - self.with_col(|col| col.transact(None, |col| col.clear_unused_tags().map(Into::into))) - } - - fn all_tags(&self, _input: pb::Empty) -> Result { - Ok(pb::StringList { - vals: self.with_col(|col| { - Ok(col - .storage - .all_tags()? - .into_iter() - .map(|t| t.name) - .collect()) - })?, - }) - } - - fn set_tag_expanded(&self, input: pb::SetTagExpandedIn) -> Result { - self.with_col(|col| { - col.transact(None, |col| { - col.set_tag_expanded(&input.name, input.expanded)?; - Ok(().into()) - }) - }) - } - - fn clear_tag(&self, tag: pb::String) -> Result { - self.with_col(|col| { - col.transact(None, |col| { - col.storage.clear_tag_and_children(tag.val.as_str())?; - Ok(().into()) - }) - }) - } - - fn tag_tree(&self, _input: pb::Empty) -> Result { - self.with_col(|col| col.tag_tree()) - } - - fn drag_drop_tags(&self, input: pb::DragDropTagsIn) -> Result { - let source_tags = input.source_tags; - let target_tag = if input.target_tag.is_empty() { - None - } else { - Some(input.target_tag) - }; - self.with_col(|col| col.drag_drop_tags(&source_tags, target_tag)) - .map(Into::into) - } } impl Backend { @@ -693,9 +486,14 @@ impl Backend { 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), + pb::ServiceIndex::NoteTypes => NoteTypesService::run_method(self, method, input), pb::ServiceIndex::Config => ConfigService::run_method(self, method, input), pb::ServiceIndex::Sync => SyncService::run_method(self, method, input), + pb::ServiceIndex::Tags => TagsService::run_method(self, method, input), + pb::ServiceIndex::DeckConfig => DeckConfigService::run_method(self, method, input), + pb::ServiceIndex::CardRendering => { + CardRenderingService::run_method(self, method, input) + } }) .map_err(|err| { let backend_err = anki_error_to_proto_error(err, &self.i18n); diff --git a/rslib/src/backend/tags.rs b/rslib/src/backend/tags.rs new file mode 100644 index 000000000..8175cdf6c --- /dev/null +++ b/rslib/src/backend/tags.rs @@ -0,0 +1,58 @@ +// 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::*}; +pub(super) use pb::tags_service::Service as TagsService; + +impl TagsService for Backend { + fn clear_unused_tags(&self, _input: pb::Empty) -> Result { + self.with_col(|col| col.transact(None, |col| col.clear_unused_tags().map(Into::into))) + } + + fn all_tags(&self, _input: pb::Empty) -> Result { + Ok(pb::StringList { + vals: self.with_col(|col| { + Ok(col + .storage + .all_tags()? + .into_iter() + .map(|t| t.name) + .collect()) + })?, + }) + } + + fn set_tag_expanded(&self, input: pb::SetTagExpandedIn) -> Result { + self.with_col(|col| { + col.transact(None, |col| { + col.set_tag_expanded(&input.name, input.expanded)?; + Ok(().into()) + }) + }) + } + + fn clear_tag(&self, tag: pb::String) -> Result { + self.with_col(|col| { + col.transact(None, |col| { + col.storage.clear_tag_and_children(tag.val.as_str())?; + Ok(().into()) + }) + }) + } + + fn tag_tree(&self, _input: pb::Empty) -> Result { + self.with_col(|col| col.tag_tree()) + } + + fn drag_drop_tags(&self, input: pb::DragDropTagsIn) -> Result { + let source_tags = input.source_tags; + let target_tag = if input.target_tag.is_empty() { + None + } else { + Some(input.target_tag) + }; + self.with_col(|col| col.drag_drop_tags(&source_tags, target_tag)) + .map(Into::into) + } +}