diff --git a/rslib/src/import_export/package/apkg/import/cards.rs b/rslib/src/import_export/package/apkg/import/cards.rs new file mode 100644 index 000000000..04704c71b --- /dev/null +++ b/rslib/src/import_export/package/apkg/import/cards.rs @@ -0,0 +1,162 @@ +// 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::{ + card::{CardQueue, CardType}, + config::SchedulerVersion, + prelude::*, +}; + +struct CardContext<'a> { + target_col: &'a mut Collection, + usn: Usn, + + conflicting_notes: &'a HashSet, + remapped_notes: &'a HashMap, + remapped_decks: &'a HashMap, + + /// The number of days the source collection is ahead of the target collection + collection_delta: i32, + scheduler_version: SchedulerVersion, + /// Cards in the target collection as (nid, ord) + targets: HashSet<(NoteId, u16)>, + /// All card ids existing in the target collection + target_ids: HashSet, + + imported_cards: HashMap, +} + +impl<'c> CardContext<'c> { + fn new<'a: 'c>( + usn: Usn, + days_elapsed: u32, + target_col: &'a mut Collection, + remapped_notes: &'a HashMap, + remapped_decks: &'a HashMap, + conflicting_notes: &'a HashSet, + ) -> Result { + let targets = target_col.storage.all_cards_as_nid_and_ord()?; + let collection_delta = target_col.collection_delta(days_elapsed)?; + let scheduler_version = target_col.scheduler_info()?.version; + let target_ids = target_col.storage.get_all_card_ids()?; + Ok(Self { + target_col, + usn, + conflicting_notes, + remapped_notes, + remapped_decks, + targets, + collection_delta, + scheduler_version, + target_ids, + imported_cards: HashMap::new(), + }) + } +} + +impl Collection { + /// How much `days_elapsed` is ahead of this collection. + fn collection_delta(&mut self, days_elapsed: u32) -> Result { + Ok(days_elapsed as i32 - self.timing_today()?.days_elapsed as i32) + } +} + +impl<'a> Context<'a> { + pub(super) fn import_cards(&mut self) -> Result> { + let mut ctx = CardContext::new( + self.usn, + self.data.days_elapsed, + self.target_col, + &self.remapped_notes, + &self.remapped_decks, + &self.conflicting_notes, + )?; + ctx.import_cards(mem::take(&mut self.data.cards))?; + Ok(ctx.imported_cards) + } +} + +impl CardContext<'_> { + pub(super) fn import_cards(&mut self, mut cards: Vec) -> Result<()> { + for card in &mut cards { + if !self.conflicting_notes.contains(&card.note_id) { + self.remap_note_id(card); + if !self.targets.contains(&(card.note_id, card.template_idx)) { + self.add_card(card)?; + } + // TODO: maybe update + } + } + Ok(()) + } + + fn add_card(&mut self, card: &mut Card) -> Result<()> { + card.usn = self.usn; + self.remap_deck_id(card); + card.shift_collection_relative_dates(self.collection_delta); + card.maybe_remove_from_filtered_deck(self.scheduler_version); + let old_id = self.uniquify_card_id(card); + + self.target_col.add_card_if_unique_undoable(card)?; + self.target_ids.insert(card.id); + self.imported_cards.insert(old_id, card.id); + + Ok(()) + } + + fn remap_note_id(&self, card: &mut Card) { + if let Some(nid) = self.remapped_notes.get(&card.note_id) { + card.note_id = *nid; + } + } + + fn uniquify_card_id(&mut self, card: &mut Card) -> CardId { + let original = card.id; + while self.target_ids.contains(&card.id) { + card.id.0 += 999; + } + original + } + + fn remap_deck_id(&self, card: &mut Card) { + if let Some(did) = self.remapped_decks.get(&card.deck_id) { + card.deck_id = *did; + } + } +} + +impl Card { + /// `delta` is the number days the card's source collection is ahead of the + /// target collection. + fn shift_collection_relative_dates(&mut self, delta: i32) { + if self.due_in_days_since_collection_creation() { + self.due -= delta; + } + if self.original_due_in_days_since_collection_creation() && self.original_due != 0 { + self.original_due -= delta; + } + } + + fn due_in_days_since_collection_creation(&self) -> bool { + matches!(self.queue, CardQueue::Review | CardQueue::DayLearn) + || self.ctype == CardType::Review + } + + fn original_due_in_days_since_collection_creation(&self) -> bool { + self.ctype == CardType::Review + } + + fn maybe_remove_from_filtered_deck(&mut self, version: SchedulerVersion) { + if self.is_filtered() { + // instead of moving between decks, the deck is converted to a regular one + self.original_deck_id = self.deck_id; + self.remove_from_filtered_deck_restoring_queue(version); + } + } +} diff --git a/rslib/src/import_export/package/apkg/import.rs b/rslib/src/import_export/package/apkg/import/mod.rs similarity index 82% rename from rslib/src/import_export/package/apkg/import.rs rename to rslib/src/import_export/package/apkg/import/mod.rs index 0f858a43e..b0254de1b 100644 --- a/rslib/src/import_export/package/apkg/import.rs +++ b/rslib/src/import_export/package/apkg/import/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 cards; + use std::{ borrow::Cow, collections::{HashMap, HashSet}, @@ -15,9 +17,7 @@ use tempfile::NamedTempFile; use zip::ZipArchive; use crate::{ - card::{CardQueue, CardType}, collection::CollectionBuilder, - config::SchedulerVersion, decks::NormalDeck, import_export::{ gather::ExchangeData, @@ -54,7 +54,6 @@ struct Context<'a> { /// exist in the target, but their notetypes don't match. conflicting_notes: HashSet, remapped_cards: HashMap, - existing_cards: HashSet, normalize_notes: bool, } @@ -134,7 +133,6 @@ impl<'a> Context<'a> { let usn = target_col.usn()?; let normalize_notes = target_col.get_config_bool(BoolKey::NormalizeNoteText); let existing_notes = target_col.storage.get_all_note_ids()?; - let existing_cards = target_col.storage.get_all_card_ids()?; Ok(Self { target_col, archive, @@ -147,7 +145,6 @@ impl<'a> Context<'a> { existing_notes, remapped_decks: HashMap::new(), remapped_cards: HashMap::new(), - existing_cards, used_media_entries: HashMap::new(), normalize_notes, }) @@ -399,58 +396,6 @@ impl<'a> Context<'a> { .get_deck_by_name(deck.name.as_native_str()) } - fn import_cards(&mut self) -> Result<()> { - let existing = self.target_col.storage.all_cards_as_nid_and_ord()?; - let collection_delta = self.collection_delta()?; - let version = self.target_col.scheduler_info()?.version; - for mut card in mem::take(&mut self.data.cards) { - if !self.conflicting_notes.contains(&card.note_id) { - self.remap_note_id(&mut card); - if !existing.contains(&(card.note_id, card.template_idx)) { - self.remap_deck_id(&mut card); - card.shift_collection_relative_dates(collection_delta); - card.maybe_remove_from_filtered_deck(version); - self.add_card(&mut card)?; - } - // TODO: maybe update - } - } - Ok(()) - } - - /// The number of days the source collection is ahead of the target collection. - fn collection_delta(&mut self) -> Result { - Ok(self.data.days_elapsed as i32 - self.target_col.timing_today()?.days_elapsed as i32) - } - - fn add_card(&mut self, card: &mut Card) -> Result<()> { - card.usn = self.usn; - self.uniquify_card_id(card); - self.target_col.add_card_if_unique_undoable(card)?; - self.existing_cards.insert(card.id); - Ok(()) - } - - fn remap_note_id(&self, card: &mut Card) { - if let Some(nid) = self.remapped_notes.get(&card.note_id) { - card.note_id = *nid; - } - } - - fn uniquify_card_id(&mut self, card: &mut Card) { - let original = card.id; - while self.existing_cards.contains(&card.id) { - card.id.0 += 999; - } - self.remapped_cards.insert(original, card.id); - } - - fn remap_deck_id(&self, card: &mut Card) { - if let Some(did) = self.remapped_decks.get(&card.deck_id) { - card.deck_id = *did; - } - } - fn import_revlog(&mut self) -> Result<()> { for mut entry in mem::take(&mut self.data.revlog) { if let Some(cid) = self.remapped_cards.get(&entry.cid) { @@ -516,33 +461,3 @@ impl Notetype { hasher.digest().bytes() } } - -impl Card { - /// `delta` is the number days the card's source collection is ahead of the - /// target collection. - fn shift_collection_relative_dates(&mut self, delta: i32) { - if self.due_in_days_since_collection_creation() { - self.due -= delta; - } - if self.original_due_in_days_since_collection_creation() && self.original_due != 0 { - self.original_due -= delta; - } - } - - fn due_in_days_since_collection_creation(&self) -> bool { - matches!(self.queue, CardQueue::Review | CardQueue::DayLearn) - || self.ctype == CardType::Review - } - - fn original_due_in_days_since_collection_creation(&self) -> bool { - self.ctype == CardType::Review - } - - fn maybe_remove_from_filtered_deck(&mut self, version: SchedulerVersion) { - if self.is_filtered() { - // instead of moving between decks, the deck is converted to a regular one - self.original_deck_id = self.deck_id; - self.remove_from_filtered_deck_restoring_queue(version); - } - } -}