From a7278836d49e22096715b6b9ad74df5a710a5eae Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 10 Apr 2022 16:07:35 +0200 Subject: [PATCH] Factor out card importing Also handle missing parents . --- .../package/apkg/import/cards.rs | 11 +- .../package/apkg/import/decks.rs | 173 ++++++++++++++++++ .../import_export/package/apkg/import/mod.rs | 93 +--------- 3 files changed, 183 insertions(+), 94 deletions(-) create mode 100644 rslib/src/import_export/package/apkg/import/decks.rs diff --git a/rslib/src/import_export/package/apkg/import/cards.rs b/rslib/src/import_export/package/apkg/import/cards.rs index dea7d724d..abf4be9fb 100644 --- a/rslib/src/import_export/package/apkg/import/cards.rs +++ b/rslib/src/import_export/package/apkg/import/cards.rs @@ -38,7 +38,7 @@ impl<'c> CardContext<'c> { days_elapsed: u32, target_col: &'a mut Collection, imported_notes: &'a HashMap, - remapped_decks: &'a HashMap, + imported_decks: &'a HashMap, ) -> Result { let targets = target_col.storage.all_cards_as_nid_and_ord()?; let collection_delta = target_col.collection_delta(days_elapsed)?; @@ -48,7 +48,7 @@ impl<'c> CardContext<'c> { target_col, usn, imported_notes, - remapped_decks, + remapped_decks: imported_decks, targets, collection_delta, scheduler_version, @@ -66,13 +66,16 @@ impl Collection { } impl<'a> Context<'a> { - pub(super) fn import_cards_and_revlog(&mut self) -> Result<()> { + pub(super) fn import_cards_and_revlog( + &mut self, + imported_decks: &HashMap, + ) -> Result<()> { let mut ctx = CardContext::new( self.usn, self.data.days_elapsed, self.target_col, &self.imported_notes, - &self.remapped_decks, + imported_decks, )?; ctx.import_cards(mem::take(&mut self.data.cards))?; ctx.import_revlog(mem::take(&mut self.data.revlog)) diff --git a/rslib/src/import_export/package/apkg/import/decks.rs b/rslib/src/import_export/package/apkg/import/decks.rs new file mode 100644 index 000000000..98138d5dd --- /dev/null +++ b/rslib/src/import_export/package/apkg/import/decks.rs @@ -0,0 +1,173 @@ +// 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}, + mem, +}; + +use super::Context; +use crate::{ + decks::{immediate_parent_name, NormalDeck}, + prelude::*, +}; + +struct DeckContext<'d> { + target_col: &'d mut Collection, + usn: Usn, + seen_parents: HashSet, + renamed_parents: Vec<(String, String)>, + imported_decks: HashMap, +} + +impl<'d> DeckContext<'d> { + fn new<'a: 'd>(target_col: &'a mut Collection, usn: Usn) -> Self { + Self { + target_col, + usn, + seen_parents: HashSet::new(), + renamed_parents: Vec::new(), + imported_decks: HashMap::new(), + } + } +} + +impl Context<'_> { + pub(super) fn import_decks_and_configs(&mut self) -> Result> { + let mut ctx = DeckContext::new(self.target_col, self.usn); + ctx.import_deck_configs(mem::take(&mut self.data.deck_configs))?; + ctx.import_decks(mem::take(&mut self.data.decks))?; + Ok(ctx.imported_decks) + } +} + +impl DeckContext<'_> { + fn import_deck_configs(&mut self, mut configs: Vec) -> Result<()> { + for config in &mut configs { + config.usn = self.usn; + self.target_col.add_deck_config_if_unique_undoable(config)?; + } + Ok(()) + } + + fn import_decks(&mut self, mut decks: Vec) -> Result<()> { + // ensure parents are seen before children + decks.sort_unstable_by(|d1, d2| d1.name.as_native_str().cmp(d2.name.as_native_str())); + for deck in &mut decks { + // TODO: handle inconsistent capitalisation + self.ensure_parents_exist(deck.name.as_native_str())?; + self.maybe_reparent(deck); + self.import_deck(deck)?; + } + Ok(()) + } + + fn ensure_parents_exist(&mut self, name: &str) -> Result<()> { + if let Some(parent) = immediate_parent_name(name) { + if !self.seen_parents.contains(parent) { + self.ensure_parents_exist(parent)?; + self.seen_parents.insert(parent.to_string()); + if let Some(new_parent) = self.reparented_name(parent) { + self.ensure_deck_exists(&new_parent)?; + } else { + self.ensure_deck_exists(parent)?; + }; + } + } + Ok(()) + } + + fn reparented_name(&self, name: &str) -> Option { + self.renamed_parents + .iter() + .find_map(|(old_parent, new_parent)| { + name.starts_with(old_parent) + .then(|| name.replacen(old_parent, new_parent, 1)) + }) + } + + fn ensure_deck_exists(&mut self, name: &str) -> Result<()> { + if let Some(deck) = self.target_col.storage.get_deck_by_name(name)? { + if deck.is_filtered() { + self.add_default_deck(name, true)?; + } + } else { + self.add_default_deck(name, false)?; + }; + Ok(()) + } + + fn add_default_deck(&mut self, name: &str, uniquify: bool) -> Result<()> { + let mut deck = Deck::new_normal(); + deck.name = NativeDeckName::from_native_str(name); + if uniquify { + self.uniquify_name(&mut deck); + } + self.target_col.add_deck_inner(&mut deck, self.usn) + } + + fn uniquify_name(&mut self, deck: &mut Deck) { + let old_parent = format!("{}\x1f", deck.name.as_native_str()); + deck.uniquify_name(); + let new_parent = format!("{}\x1f", deck.name.as_native_str()); + self.renamed_parents.push((old_parent, new_parent)); + } + + fn maybe_reparent(&self, deck: &mut Deck) { + if let Some(new_name) = self.reparented_name(deck.name.as_native_str()) { + deck.name = NativeDeckName::from_native_str(new_name); + } + } + + fn import_deck(&mut self, deck: &mut Deck) -> Result<()> { + if let Some(original) = self.get_deck_by_name(deck)? { + if original.is_filtered() { + self.uniquify_name(deck); + self.add_deck(deck) + } else { + self.update_deck(deck, original) + } + } else { + self.add_deck(deck) + } + } + + fn get_deck_by_name(&mut self, deck: &Deck) -> Result> { + self.target_col + .storage + .get_deck_by_name(deck.name.as_native_str()) + } + + fn add_deck(&mut self, deck: &mut Deck) -> Result<()> { + let old_id = mem::take(&mut deck.id); + self.target_col.add_deck_inner(deck, self.usn)?; + self.imported_decks.insert(old_id, deck.id); + Ok(()) + } + + /// Caller must ensure decks are normal. + fn update_deck(&mut self, deck: &Deck, original: Deck) -> Result<()> { + let mut new_deck = original.clone(); + new_deck.normal_mut()?.update_with_other(deck.normal()?); + self.imported_decks.insert(deck.id, new_deck.id); + self.target_col + .update_deck_inner(&mut new_deck, original, self.usn) + } +} + +impl Deck { + fn uniquify_name(&mut self) { + let new_name = format!("{} {}", self.name.as_native_str(), TimestampSecs::now()); + self.name = NativeDeckName::from_native_str(new_name); + } +} + +impl NormalDeck { + fn update_with_other(&mut self, other: &Self) { + self.markdown_description = other.markdown_description; + self.description = other.description.clone(); + if other.config_id != 1 { + self.config_id = other.config_id; + } + } +} diff --git a/rslib/src/import_export/package/apkg/import/mod.rs b/rslib/src/import_export/package/apkg/import/mod.rs index 0fe0c5c54..41b84426c 100644 --- a/rslib/src/import_export/package/apkg/import/mod.rs +++ b/rslib/src/import_export/package/apkg/import/mod.rs @@ -2,6 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html mod cards; +mod decks; use std::{ borrow::Cow, @@ -18,7 +19,6 @@ use zip::ZipArchive; use crate::{ collection::CollectionBuilder, - decks::NormalDeck, import_export::{ gather::ExchangeData, package::{ @@ -42,7 +42,6 @@ struct Context<'a> { remapped_notetypes: HashMap, imported_notes: HashMap, existing_notes: HashSet, - remapped_decks: HashMap, data: ExchangeData, usn: Usn, /// Map of source media files, that do not already exist in the target. @@ -138,7 +137,6 @@ impl<'a> Context<'a> { remapped_notetypes: HashMap::new(), imported_notes: HashMap::new(), existing_notes, - remapped_decks: HashMap::new(), used_media_entries: HashMap::new(), normalize_notes, }) @@ -148,9 +146,8 @@ impl<'a> Context<'a> { self.prepare_media()?; self.import_notetypes()?; self.import_notes()?; - self.import_deck_configs()?; - self.import_decks()?; - self.import_cards_and_revlog()?; + let imported_decks = self.import_decks_and_configs()?; + self.import_cards_and_revlog(&imported_decks)?; self.copy_media() } @@ -336,58 +333,6 @@ impl<'a> Context<'a> { }) } - fn import_deck_configs(&mut self) -> Result<()> { - for mut config in mem::take(&mut self.data.deck_configs) { - config.usn = self.usn; - self.target_col - .add_deck_config_if_unique_undoable(&config)?; - } - Ok(()) - } - - fn import_decks(&mut self) -> Result<()> { - // TODO: ensure alphabetical order, so parents are seen first - let mut renamed_parents = Vec::new(); - - for mut deck in mem::take(&mut self.data.decks) { - deck.maybe_reparent(&renamed_parents); - if let Some(original) = self.get_deck_by_name(&deck)? { - if original.is_filtered() { - deck.uniquify_name(&mut renamed_parents); - self.add_deck(&mut deck)?; - } else { - self.update_deck(&deck, original)?; - } - } else { - self.add_deck(&mut deck)?; - } - } - - Ok(()) - } - - fn add_deck(&mut self, deck: &mut Deck) -> Result<()> { - let old_id = mem::take(&mut deck.id); - self.target_col.add_deck_inner(deck, self.usn)?; - self.remapped_decks.insert(old_id, deck.id); - Ok(()) - } - - /// Caller must ensure decks are normal. - fn update_deck(&mut self, deck: &Deck, original: Deck) -> Result<()> { - let mut new_deck = original.clone(); - new_deck.normal_mut()?.update_with_other(deck.normal()?); - self.remapped_decks.insert(deck.id, new_deck.id); - self.target_col - .update_deck_inner(&mut new_deck, original, self.usn) - } - - fn get_deck_by_name(&mut self, deck: &Deck) -> Result> { - self.target_col - .storage - .get_deck_by_name(deck.name.as_native_str()) - } - fn copy_media(&mut self) -> Result<()> { for (used, entry) in self.used_media_entries.values() { if *used { @@ -398,38 +343,6 @@ impl<'a> Context<'a> { } } -impl Deck { - fn maybe_reparent(&mut self, renamed_parents: &[(String, String)]) { - if let Some(new_name) = reparented_name(self.name.as_native_str(), renamed_parents) { - self.name = NativeDeckName::from_native_str(new_name); - } - } - - fn uniquify_name(&mut self, renamed_parents: &mut Vec<(String, String)>) { - let name = self.name.as_native_str(); - let new_name = format!("{name} {}", TimestampSecs::now()); - renamed_parents.push((format!("{name}\x1f"), format!("{new_name}\x1f"))); - self.name = NativeDeckName::from_native_str(new_name); - } -} - -impl NormalDeck { - fn update_with_other(&mut self, other: &Self) { - self.markdown_description = other.markdown_description; - self.description = other.description.clone(); - if other.config_id != 1 { - self.config_id = other.config_id; - } - } -} - -fn reparented_name(name: &str, renamed_parents: &[(String, String)]) -> Option { - renamed_parents.iter().find_map(|(old_parent, new_parent)| { - name.starts_with(old_parent) - .then(|| name.replacen(old_parent, new_parent, 1)) - }) -} - impl Notetype { fn schema_hash(&self) -> [u8; 20] { let mut hasher = Sha1::new();