From 84c5ac8a0a99a304add68d0f4484e6d3fece3be2 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 29 Mar 2022 16:47:57 +0200 Subject: [PATCH] Gather export data instead of copying directly --- rslib/src/import_export/gather.rs | 284 ++++++++++++++++ rslib/src/import_export/insert.rs | 61 ++++ rslib/src/import_export/mod.rs | 2 + .../src/import_export/package/apkg/export.rs | 302 ++---------------- .../package/apkg/export_cards.sql | 7 - .../package/apkg/export_deck_configs.sql | 4 - .../package/apkg/export_decks.sql | 4 - .../package/apkg/export_notes.sql | 7 - .../package/apkg/export_notetypes.sql | 7 - .../package/apkg/export_revlog.sql | 7 - .../package/apkg/export_siblings.sql | 30 -- .../package/apkg/non_new_cards.sql | 4 - .../package/apkg/reset_cards.sql | 8 - rslib/src/search/builder.rs | 8 + rslib/src/storage/revlog/mod.rs | 7 + 15 files changed, 388 insertions(+), 354 deletions(-) create mode 100644 rslib/src/import_export/gather.rs create mode 100644 rslib/src/import_export/insert.rs delete mode 100644 rslib/src/import_export/package/apkg/export_cards.sql delete mode 100644 rslib/src/import_export/package/apkg/export_deck_configs.sql delete mode 100644 rslib/src/import_export/package/apkg/export_decks.sql delete mode 100644 rslib/src/import_export/package/apkg/export_notes.sql delete mode 100644 rslib/src/import_export/package/apkg/export_notetypes.sql delete mode 100644 rslib/src/import_export/package/apkg/export_revlog.sql delete mode 100644 rslib/src/import_export/package/apkg/export_siblings.sql delete mode 100644 rslib/src/import_export/package/apkg/non_new_cards.sql delete mode 100644 rslib/src/import_export/package/apkg/reset_cards.sql diff --git a/rslib/src/import_export/gather.rs b/rslib/src/import_export/gather.rs new file mode 100644 index 000000000..38251d3bb --- /dev/null +++ b/rslib/src/import_export/gather.rs @@ -0,0 +1,284 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, +}; + +use itertools::Itertools; + +use crate::{ + card::{CardQueue, CardType}, + decks::NormalDeck, + latex::extract_latex, + prelude::*, + revlog::RevlogEntry, + search::{Negated, SearchNode, SortMode}, + storage::ids_to_string, + text::{ + extract_media_refs, extract_underscored_css_imports, extract_underscored_references, + is_remote_filename, + }, +}; + +#[derive(Debug, Default)] +pub(super) struct ExportData { + pub(super) decks: Vec, + pub(super) notes: Vec, + pub(super) cards: Vec, + pub(super) notetypes: Vec, + pub(super) revlog: Vec, + pub(super) decks_configs: Vec, + pub(super) media_paths: HashSet, +} + +fn sibling_search(notes: &[Note], cards: &[Card]) -> SearchBuilder { + let mut nids = String::new(); + ids_to_string(&mut nids, notes.iter().map(|note| note.id)); + let mut cids = String::new(); + ids_to_string(&mut cids, cards.iter().map(|card| card.id)); + SearchBuilder::from(SearchNode::NoteIds(nids)).and(SearchNode::CardIds(cids).negated()) +} + +fn optional_deck_search(deck_id: Option) -> SearchNode { + if let Some(did) = deck_id { + SearchNode::from_deck_id(did, true) + } else { + SearchNode::WholeCollection + } +} + +fn is_local_base_name(name: &str) -> bool { + !is_remote_filename(name) && Path::new(name).parent().is_none() +} + +impl ExportData { + /* + pub(super) fn new(, media_folder: Option) -> Self { + Self { + with_scheduling, + media_folder, + ..Default::default() + } + } + */ + pub(super) fn gather_data( + &mut self, + col: &mut Collection, + deck_id: Option, + with_scheduling: bool, + ) -> Result<()> { + self.decks = col.gather_decks(deck_id)?; + let search = optional_deck_search(deck_id); + self.notes = col.gather_notes(search.clone())?; + self.cards = col.gather_cards(search, &self.notes, deck_id)?; + self.notetypes = col.gather_notetypes(&self.notes)?; + + if with_scheduling { + self.revlog = col.gather_revlog(&self.cards)?; + self.decks_configs = col.gather_deck_configs(&self.decks)?; + } else { + self.remove_scheduling_information(col); + }; + + Ok(()) + } + + pub(super) fn gather_media_paths(&mut self, media_folder: &Path) { + let mut inserter = |name: &str| { + if is_local_base_name(name) { + self.media_paths.insert(media_folder.join(name)); + } + }; + let svg_getter = svg_getter(&self.notetypes); + for note in self.notes.iter() { + gather_media_paths_from_note(note, &mut inserter, &svg_getter); + } + for notetype in self.notetypes.iter() { + gather_media_paths_from_notetype(notetype, &mut inserter); + } + } + + fn remove_scheduling_information(&mut self, col: &Collection) { + self.remove_system_tags(); + self.reset_deck_config_ids(); + self.reset_cards(col); + } + + fn remove_system_tags(&mut self) { + // TODO: case folding? child tags? + for note in self.notes.iter_mut() { + note.tags = std::mem::take(&mut note.tags) + .into_iter() + .filter(|tag| !matches!(tag.as_str(), "marked" | "leech")) + .collect(); + } + } + + fn reset_deck_config_ids(&mut self) { + for deck in self.decks.iter_mut() { + if let Ok(normal_mut) = deck.normal_mut() { + normal_mut.config_id = 1; + } else { + // TODO: scheduling case + deck.kind = DeckKind::Normal(NormalDeck { + config_id: 1, + ..Default::default() + }) + } + } + } + + fn reset_cards(&mut self, col: &Collection) { + let mut position = col.get_next_card_position(); + for card in self.cards.iter_mut() { + if card.ctype != CardType::New || card.queue != CardQueue::New { + card.due = card.original_position.unwrap_or_else(|| { + position += 1; + position - 1 + }) as i32; + } + card.interval = 0; + card.ease_factor = 0; + card.reps = 0; + card.lapses = 0; + card.original_deck_id = DeckId(0); + card.original_due = 0; + card.original_position = None; + card.queue = CardQueue::New; + card.ctype = CardType::New; + card.flags = 0; + } + } +} + +fn gather_media_paths_from_note( + note: &Note, + inserter: &mut impl FnMut(&str), + svg_getter: &impl Fn(NotetypeId) -> bool, +) { + for field in note.fields() { + for media_ref in extract_media_refs(field) { + inserter(&media_ref.fname_decoded); + } + + for latex in extract_latex(field, svg_getter(note.notetype_id)).1 { + inserter(&latex.fname); + } + } +} + +fn gather_media_paths_from_notetype(notetype: &Notetype, inserter: &mut impl FnMut(&str)) { + for name in extract_underscored_css_imports(¬etype.config.css) { + inserter(name); + } + for template in ¬etype.templates { + for template_side in [&template.config.q_format, &template.config.a_format] { + for name in extract_underscored_references(template_side) { + inserter(name); + } + } + } +} + +fn svg_getter(notetypes: &[Notetype]) -> impl Fn(NotetypeId) -> bool { + let svg_map: HashMap = notetypes + .iter() + .map(|nt| (nt.id, nt.config.latex_svg)) + .collect(); + move |nt_id| svg_map.get(&nt_id).copied().unwrap_or_default() +} + +impl Collection { + fn gather_decks(&mut self, deck_id: Option) -> Result> { + if let Some(did) = deck_id { + let deck = self.get_deck(did)?.ok_or(AnkiError::NotFound)?; + self.storage + .deck_id_with_children(&deck)? + .iter() + .filter(|did| **did != DeckId(1)) + .map(|did| self.storage.get_deck(*did)?.ok_or(AnkiError::NotFound)) + .collect() + } else { + Ok(self + .storage + .get_all_decks()? + .into_iter() + .filter(|deck| deck.id != DeckId(1)) + .collect()) + } + } + + fn gather_notes(&mut self, search: SearchNode) -> Result> { + self.search_notes(search, SortMode::NoOrder)? + .iter() + .map(|nid| self.storage.get_note(*nid)?.ok_or(AnkiError::NotFound)) + .collect() + } + + fn gather_cards( + &mut self, + search: SearchNode, + notes: &[Note], + deck_id: Option, + ) -> Result> { + let mut cards: Vec<_> = self + .search_cards(search, SortMode::NoOrder)? + .iter() + .map(|cid| self.storage.get_card(*cid)?.ok_or(AnkiError::NotFound)) + .collect::>()?; + + if let Some(did) = deck_id { + let mut siblings = self.gather_siblings(notes, &cards, did)?; + cards.append(&mut siblings); + } + + Ok(cards) + } + + fn gather_siblings( + &mut self, + notes: &[Note], + cards: &[Card], + deck_id: DeckId, + ) -> Result> { + self.search_cards(sibling_search(notes, cards), SortMode::NoOrder)? + .iter() + .map(|cid| { + let mut card = self.storage.get_card(*cid)?.ok_or(AnkiError::NotFound)?; + card.deck_id = deck_id; + Ok(card) + }) + .collect() + } + + fn gather_notetypes(&mut self, notes: &[Note]) -> Result> { + notes + .iter() + .map(|note| note.notetype_id) + .unique() + .map(|ntid| self.storage.get_notetype(ntid)?.ok_or(AnkiError::NotFound)) + .collect() + } + + fn gather_revlog(&mut self, cards: &[Card]) -> Result> { + let mut cids = String::new(); + ids_to_string(&mut cids, cards.iter().map(|card| card.id)); + self.storage.get_revlog_entries_for_card_ids(cids) + } + + fn gather_deck_configs(&mut self, decks: &[Deck]) -> Result> { + decks + .iter() + .filter_map(|deck| deck.config_id()) + .unique() + .filter(|config_id| *config_id != DeckConfigId(1)) + .map(|config_id| { + self.storage + .get_deck_config(config_id)? + .ok_or(AnkiError::NotFound) + }) + .collect() + } +} diff --git a/rslib/src/import_export/insert.rs b/rslib/src/import_export/insert.rs new file mode 100644 index 000000000..a4ae670f5 --- /dev/null +++ b/rslib/src/import_export/insert.rs @@ -0,0 +1,61 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::gather::ExportData; +use crate::{prelude::*, revlog::RevlogEntry}; + +impl Collection { + pub(super) fn insert_data(&mut self, data: &ExportData) -> Result<()> { + self.insert_decks(&data.decks)?; + self.insert_notes(&data.notes)?; + self.insert_cards(&data.cards)?; + self.insert_notetypes(&data.notetypes)?; + self.insert_revlog(&data.revlog)?; + self.insert_deck_configs(&data.decks_configs)?; + Ok(()) + } + + fn insert_decks(&mut self, decks: &[Deck]) -> Result<()> { + for deck in decks { + self.storage.add_or_update_deck_with_existing_id(deck)?; + } + Ok(()) + } + + fn insert_notes(&mut self, notes: &[Note]) -> Result<()> { + for note in notes { + self.storage.add_or_update_note(note)?; + } + Ok(()) + } + + fn insert_cards(&mut self, cards: &[Card]) -> Result<()> { + for card in cards { + self.storage.add_or_update_card(card)?; + } + Ok(()) + } + + fn insert_notetypes(&mut self, notetypes: &[Notetype]) -> Result<()> { + for notetype in notetypes { + self.storage + .add_or_update_notetype_with_existing_id(notetype)?; + } + Ok(()) + } + + fn insert_revlog(&mut self, revlog: &[RevlogEntry]) -> Result<()> { + for entry in revlog { + self.storage.add_revlog_entry(entry, false)?; + } + Ok(()) + } + + fn insert_deck_configs(&mut self, configs: &[DeckConfig]) -> Result<()> { + for config in configs { + self.storage + .add_or_update_deck_config_with_existing_id(config)?; + } + Ok(()) + } +} diff --git a/rslib/src/import_export/mod.rs b/rslib/src/import_export/mod.rs index 994d93101..324a11186 100644 --- a/rslib/src/import_export/mod.rs +++ b/rslib/src/import_export/mod.rs @@ -1,6 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +mod gather; +mod insert; pub mod package; #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/rslib/src/import_export/package/apkg/export.rs b/rslib/src/import_export/package/apkg/export.rs index c485fa2a3..4d64e415f 100644 --- a/rslib/src/import_export/package/apkg/export.rs +++ b/rslib/src/import_export/package/apkg/export.rs @@ -1,31 +1,22 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use std::{ - collections::{HashMap, HashSet}, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::path::{Path, PathBuf}; -use rusqlite::{named_params, params}; use tempfile::NamedTempFile; use crate::{ collection::CollectionBuilder, - import_export::package::{ - colpkg::export::{export_collection, MediaIter}, - Meta, + import_export::{ + gather::ExportData, + package::{ + colpkg::export::{export_collection, MediaIter}, + Meta, + }, }, io::{atomic_rename, tempfile_in_parent_of}, - latex::extract_latex, - notetype::CardTemplate, prelude::*, - storage::{ids_to_string, SchemaVersion, SqliteStorage}, - tags::matcher::TagMatcher, - text::{ - extract_media_refs, extract_underscored_css_imports, extract_underscored_references, - is_remote_filename, - }, + storage::SchemaVersion, }; impl Collection { @@ -33,8 +24,8 @@ impl Collection { &mut self, out_path: impl AsRef, deck_id: Option, - include_scheduling: bool, - include_media: bool, + with_scheduling: bool, + with_media: bool, progress_fn: impl FnMut(usize), ) -> Result<()> { let temp_apkg = tempfile_in_parent_of(out_path.as_ref())?; @@ -46,8 +37,8 @@ impl Collection { let media = self.export_collection_extracting_media( temp_col_path, deck_id, - include_scheduling, - include_media, + with_scheduling, + with_media, )?; let col_size = temp_col.as_file().metadata()?.len() as usize; @@ -67,266 +58,25 @@ impl Collection { &mut self, path: &str, deck_id: Option, - include_scheduling: bool, - include_media: bool, + with_scheduling: bool, + with_media: bool, ) -> Result { - CollectionBuilder::new(path).build()?.close(None)?; - self.export_into_other(path, deck_id, include_scheduling)?; + let mut data = ExportData::default(); + data.gather_data(self, deck_id, with_scheduling)?; + if with_media { + data.gather_media_paths(&self.media_folder); + } - let mut temp_col = CollectionBuilder::new(path).build()?; - if !include_scheduling { - temp_col.remove_scheduling_information()?; - } - let mut media = HashSet::new(); - if include_media { - temp_col.extract_media_paths(&mut media)?; - } + let mut temp_col = Collection::new_minimal(path)?; + temp_col.insert_data(&data)?; temp_col.close(Some(SchemaVersion::V11))?; - Ok(MediaIter::from_file_list(media)) + Ok(MediaIter::from_file_list(data.media_paths)) } - fn export_into_other( - &mut self, - other_path: &str, - deck_id: Option, - export_scheduling_tables: bool, - ) -> Result<()> { - self.storage - .db - .execute("ATTACH ? AS other", params!(other_path))?; - let res = self.export_into_other_inner(deck_id, export_scheduling_tables); - self.storage.db.execute_batch("DETACH other")?; - - res - } - - fn export_into_other_inner( - &mut self, - deck_id: Option, - export_scheduling_tables: bool, - ) -> Result<()> { - self.export_decks(deck_id)?; - self.storage.export_cards(deck_id)?; - self.storage.export_notes()?; - self.storage.export_notetypes()?; - if export_scheduling_tables { - self.storage.export_revlog()?; - self.storage.export_deck_configs()?; - } - Ok(()) - } - - fn export_decks(&mut self, deck_id: Option) -> Result<()> { - let sql = if let Some(did) = deck_id { - self.export_deck_sql(did)? - } else { - include_str!("export_decks.sql").into() - }; - self.storage.db.execute_batch(&sql)?; - Ok(()) - } - - fn export_deck_sql(&mut self, did: DeckId) -> Result { - let mut sql = format!("{} AND id IN ", include_str!("export_decks.sql")); - let deck = self.get_deck(did)?.ok_or(AnkiError::NotFound)?; - let ids = self.storage.deck_id_with_children(&deck)?; - ids_to_string(&mut sql, &ids); - Ok(sql) - } - - fn remove_scheduling_information(&mut self) -> Result<()> { - self.storage.remove_system_tags()?; - self.reset_deck_config_ids()?; - self.reset_cards() - } - - fn reset_deck_config_ids(&mut self) -> Result<()> { - for mut deck in self.storage.get_all_decks()? { - deck.normal_mut()?.config_id = 1; - self.update_deck(&mut deck)?; - } - Ok(()) - } - - fn reset_cards(&mut self) -> Result<()> { - let cids = self.storage.get_non_new_card_ids()?; - self.reschedule_cards_as_new(&cids, false, true, false, None)?; - self.storage - .db - .execute_batch(include_str!("reset_cards.sql"))?; - Ok(()) - } - - fn extract_media_paths(&mut self, names: &mut HashSet) -> Result<()> { - let notetypes = self.get_all_notetypes()?; - self.extract_media_paths_from_notes(names, ¬etypes)?; - self.extract_media_paths_from_notetypes(names, ¬etypes); - Ok(()) - } - - fn extract_media_paths_from_notes( - &mut self, - names: &mut HashSet, - notetypes: &HashMap>, - ) -> Result<()> { - let mut stmt = self.storage.db.prepare("SELECT flds, mid FROM notes")?; - let mut rows = stmt.query([])?; - while let Some(row) = rows.next()? { - let flds = row.get_ref(0)?.as_str()?; - let notetype_id: NotetypeId = row.get(1)?; - self.extract_media_paths_from_note(names, flds, notetypes.get(¬etype_id).unwrap()); - } - Ok(()) - } - - fn extract_media_paths_from_note( - &self, - names: &mut HashSet, - flds: &str, - notetype: &Notetype, - ) { - self.extract_latex_paths(names, flds, notetype); - for media_ref in extract_media_refs(flds) { - if is_local_base_name(&media_ref.fname_decoded) { - names.insert(self.media_folder.join(media_ref.fname_decoded.as_ref())); - } - } - } - - fn extract_latex_paths(&self, names: &mut HashSet, flds: &str, notetype: &Notetype) { - for latex in extract_latex(flds, notetype.config.latex_svg).1 { - if is_local_base_name(&latex.fname) { - names.insert(self.media_folder.join(&latex.fname)); - } - } - } - - fn extract_media_paths_from_notetypes( - &mut self, - names: &mut HashSet, - notetypes: &HashMap>, - ) { - for notetype in notetypes.values() { - notetype.extract_media_paths(names, &self.media_folder); - } - } -} - -fn is_local_base_name(name: &str) -> bool { - !is_remote_filename(name) && Path::new(name).parent().is_none() -} - -impl Notetype { - fn extract_media_paths(&self, names: &mut HashSet, media_folder: &Path) { - for name in extract_underscored_css_imports(&self.config.css) { - if is_local_base_name(name) { - names.insert(media_folder.join(name)); - } - } - for template in &self.templates { - template.extract_media_paths(names, media_folder); - } - } -} - -impl CardTemplate { - fn extract_media_paths(&self, names: &mut HashSet, media_folder: &Path) { - for template_side in [&self.config.q_format, &self.config.a_format] { - for name in extract_underscored_references(template_side) { - if is_local_base_name(name) { - names.insert(media_folder.join(name)); - } - } - } - } -} - -impl SqliteStorage { - fn export_cards(&mut self, deck_id: Option) -> Result<()> { - self.db.execute_batch(include_str!("export_cards.sql"))?; - if let Some(did) = deck_id { - // include siblings outside the exported deck, because they would - // get created on import anyway - self.db.execute( - include_str!("export_siblings.sql"), - named_params! {"did": did}, - )?; - } - Ok(()) - } - - fn export_notes(&mut self) -> Result<()> { - self.db.execute_batch(include_str!("export_notes.sql"))?; - Ok(()) - } - - fn export_notetypes(&mut self) -> Result<()> { - self.db.execute_batch("DELETE FROM other.notetypes")?; - self.db - .execute_batch(include_str!("export_notetypes.sql"))?; - Ok(()) - } - - fn export_revlog(&mut self) -> Result<()> { - self.db.execute_batch(include_str!("export_revlog.sql"))?; - Ok(()) - } - - fn export_deck_configs(&mut self) -> Result<()> { - let id_string = self.exported_deck_config_ids()?; - self.db.execute( - include_str!("export_deck_configs.sql"), - named_params! {"ids": id_string}, - )?; - Ok(()) - } - - fn exported_deck_config_ids(&mut self) -> Result { - let all_decks = self.get_all_decks()?; - let exported_deck_ids = self.exported_deck_ids()?; - - let ids = all_decks - .iter() - .filter(|deck| exported_deck_ids.contains(&deck.id)) - .filter_map(|deck| deck.config_id()); - let mut id_string = String::new(); - ids_to_string(&mut id_string, ids); - - Ok(id_string) - } - - fn exported_deck_ids(&mut self) -> Result> { - self.db - .prepare("SELECT DISTINCT id FROM other.decks")? - .query_and_then([], |row| Ok(DeckId(row.get(0)?)))? - .collect() - } - - fn remove_system_tags(&mut self) -> Result<()> { - let mut matcher = TagMatcher::new("marked leech")?; - let mut rows_stmt = self.db.prepare("SELECT id, tags FROM notes")?; - let mut update_stmt = self - .db - .prepare_cached("UPDATE notes SET tags = ? WHERE id = ?")?; - - let mut rows = rows_stmt.query(params![])?; - while let Some(row) = rows.next()? { - let tags = row.get_ref(1)?.as_str()?; - if matcher.is_match(tags) { - let new_tags = matcher.remove(tags); - let note_id: NoteId = row.get(0)?; - update_stmt.execute(params![new_tags, note_id])?; - } - } - - Ok(()) - } - - fn get_non_new_card_ids(&self) -> Result> { - self.db - .prepare(include_str!("non_new_cards.sql"))? - .query_and_then([], |row| Ok(CardId(row.get(0)?)))? - .collect() + fn new_minimal(path: impl Into) -> Result { + let col = CollectionBuilder::new(path).build()?; + col.storage.db.execute_batch("DELETE FROM notetypes")?; + Ok(col) } } diff --git a/rslib/src/import_export/package/apkg/export_cards.sql b/rslib/src/import_export/package/apkg/export_cards.sql deleted file mode 100644 index d0942045f..000000000 --- a/rslib/src/import_export/package/apkg/export_cards.sql +++ /dev/null @@ -1,7 +0,0 @@ -INSERT INTO other.cards -SELECT * -FROM cards -WHERE did IN ( - SELECT did - FROM other.decks - ) \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_deck_configs.sql b/rslib/src/import_export/package/apkg/export_deck_configs.sql deleted file mode 100644 index 95a32d3f6..000000000 --- a/rslib/src/import_export/package/apkg/export_deck_configs.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO other.deck_config -SELECT * -FROM deck_config -WHERE id IN :ids \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_decks.sql b/rslib/src/import_export/package/apkg/export_decks.sql deleted file mode 100644 index 25ffd8a96..000000000 --- a/rslib/src/import_export/package/apkg/export_decks.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO other.decks -SELECT * -FROM decks -WHERE id != 1 \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_notes.sql b/rslib/src/import_export/package/apkg/export_notes.sql deleted file mode 100644 index 5276d6c25..000000000 --- a/rslib/src/import_export/package/apkg/export_notes.sql +++ /dev/null @@ -1,7 +0,0 @@ -INSERT INTO other.notes -SELECT * -FROM notes -WHERE id IN ( - SELECT DISTINCT nid - FROM other.cards - ) \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_notetypes.sql b/rslib/src/import_export/package/apkg/export_notetypes.sql deleted file mode 100644 index d736dd35f..000000000 --- a/rslib/src/import_export/package/apkg/export_notetypes.sql +++ /dev/null @@ -1,7 +0,0 @@ -INSERT INTO other.notetypes -SELECT * -FROM notetypes -WHERE id IN ( - SELECT DISTINCT mid - FROM other.notes - ) \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_revlog.sql b/rslib/src/import_export/package/apkg/export_revlog.sql deleted file mode 100644 index 21e4e303f..000000000 --- a/rslib/src/import_export/package/apkg/export_revlog.sql +++ /dev/null @@ -1,7 +0,0 @@ -INSERT INTO other.revlog -SELECT * -FROM revlog -WHERE cid IN ( - SELECT cid - FROM other.cards - ) \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/export_siblings.sql b/rslib/src/import_export/package/apkg/export_siblings.sql deleted file mode 100644 index 9697519f6..000000000 --- a/rslib/src/import_export/package/apkg/export_siblings.sql +++ /dev/null @@ -1,30 +0,0 @@ -INSERT INTO other.cards -SELECT ( - id, - nid, - :did, - ord, - mod, - usn, - type, - queue, - due, - ivl, - factor, - reps, - lapses, - left, - odue, - odid, - flags, - data - ) -FROM cards -WHERE id NOT IN ( - SELECT id - FROM other.cards - ) - AND nid IN ( - SELECT DISTINCT nid - FROM other.cards - ) \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/non_new_cards.sql b/rslib/src/import_export/package/apkg/non_new_cards.sql deleted file mode 100644 index 371911cb7..000000000 --- a/rslib/src/import_export/package/apkg/non_new_cards.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT id -FROM cards -WHERE queue != 0 - OR type != 0 \ No newline at end of file diff --git a/rslib/src/import_export/package/apkg/reset_cards.sql b/rslib/src/import_export/package/apkg/reset_cards.sql deleted file mode 100644 index 0bce73ef4..000000000 --- a/rslib/src/import_export/package/apkg/reset_cards.sql +++ /dev/null @@ -1,8 +0,0 @@ -UPDATE cards -SET reps = 0, - lapses = 0, - odid = 0, - odue = 0, - queue = 0, - type = 0, - flags = 0 \ No newline at end of file diff --git a/rslib/src/search/builder.rs b/rslib/src/search/builder.rs index de4560b3e..e5c4e6067 100644 --- a/rslib/src/search/builder.rs +++ b/rslib/src/search/builder.rs @@ -127,6 +127,14 @@ impl Default for SearchBuilder { } impl SearchNode { + pub fn from_deck_id(did: DeckId, with_children: bool) -> Self { + if with_children { + Self::DeckIdWithChildren(did) + } else { + Self::DeckIdWithoutChildren(did) + } + } + /// Construct [SearchNode] from an unescaped deck name. pub fn from_deck_name(name: &str) -> Self { Self::Deck(escape_anki_wildcards_for_search_node(name)) diff --git a/rslib/src/storage/revlog/mod.rs b/rslib/src/storage/revlog/mod.rs index 4284b25be..c70b56cda 100644 --- a/rslib/src/storage/revlog/mod.rs +++ b/rslib/src/storage/revlog/mod.rs @@ -120,6 +120,13 @@ impl SqliteStorage { .collect() } + pub(crate) fn get_revlog_entries_for_card_ids(&self, cids: String) -> Result> { + self.db + .prepare_cached(&format!("{} where cid in {cids}", include_str!("get.sql"),))? + .query_and_then([], row_to_revlog_entry)? + .collect() + } + /// This includes entries from deleted cards. pub(crate) fn get_all_revlog_entries( &self,