mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Factor out card importing
This commit is contained in:
parent
e1899152be
commit
8348240f80
2 changed files with 164 additions and 87 deletions
162
rslib/src/import_export/package/apkg/import/cards.rs
Normal file
162
rslib/src/import_export/package/apkg/import/cards.rs
Normal file
|
@ -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<NoteId>,
|
||||
remapped_notes: &'a HashMap<NoteId, NoteId>,
|
||||
remapped_decks: &'a HashMap<DeckId, DeckId>,
|
||||
|
||||
/// 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<CardId>,
|
||||
|
||||
imported_cards: HashMap<CardId, CardId>,
|
||||
}
|
||||
|
||||
impl<'c> CardContext<'c> {
|
||||
fn new<'a: 'c>(
|
||||
usn: Usn,
|
||||
days_elapsed: u32,
|
||||
target_col: &'a mut Collection,
|
||||
remapped_notes: &'a HashMap<NoteId, NoteId>,
|
||||
remapped_decks: &'a HashMap<DeckId, DeckId>,
|
||||
conflicting_notes: &'a HashSet<NoteId>,
|
||||
) -> Result<Self> {
|
||||
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<i32> {
|
||||
Ok(days_elapsed as i32 - self.timing_today()?.days_elapsed as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub(super) fn import_cards(&mut self) -> Result<HashMap<CardId, CardId>> {
|
||||
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<Card>) -> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<NoteId>,
|
||||
remapped_cards: HashMap<CardId, CardId>,
|
||||
existing_cards: HashSet<CardId>,
|
||||
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<i32> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue