mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Factor out card importing
Also handle missing parents .
This commit is contained in:
parent
a402879e72
commit
a7278836d4
3 changed files with 183 additions and 94 deletions
|
@ -38,7 +38,7 @@ impl<'c> CardContext<'c> {
|
||||||
days_elapsed: u32,
|
days_elapsed: u32,
|
||||||
target_col: &'a mut Collection,
|
target_col: &'a mut Collection,
|
||||||
imported_notes: &'a HashMap<NoteId, NoteId>,
|
imported_notes: &'a HashMap<NoteId, NoteId>,
|
||||||
remapped_decks: &'a HashMap<DeckId, DeckId>,
|
imported_decks: &'a HashMap<DeckId, DeckId>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let targets = target_col.storage.all_cards_as_nid_and_ord()?;
|
let targets = target_col.storage.all_cards_as_nid_and_ord()?;
|
||||||
let collection_delta = target_col.collection_delta(days_elapsed)?;
|
let collection_delta = target_col.collection_delta(days_elapsed)?;
|
||||||
|
@ -48,7 +48,7 @@ impl<'c> CardContext<'c> {
|
||||||
target_col,
|
target_col,
|
||||||
usn,
|
usn,
|
||||||
imported_notes,
|
imported_notes,
|
||||||
remapped_decks,
|
remapped_decks: imported_decks,
|
||||||
targets,
|
targets,
|
||||||
collection_delta,
|
collection_delta,
|
||||||
scheduler_version,
|
scheduler_version,
|
||||||
|
@ -66,13 +66,16 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
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<DeckId, DeckId>,
|
||||||
|
) -> Result<()> {
|
||||||
let mut ctx = CardContext::new(
|
let mut ctx = CardContext::new(
|
||||||
self.usn,
|
self.usn,
|
||||||
self.data.days_elapsed,
|
self.data.days_elapsed,
|
||||||
self.target_col,
|
self.target_col,
|
||||||
&self.imported_notes,
|
&self.imported_notes,
|
||||||
&self.remapped_decks,
|
imported_decks,
|
||||||
)?;
|
)?;
|
||||||
ctx.import_cards(mem::take(&mut self.data.cards))?;
|
ctx.import_cards(mem::take(&mut self.data.cards))?;
|
||||||
ctx.import_revlog(mem::take(&mut self.data.revlog))
|
ctx.import_revlog(mem::take(&mut self.data.revlog))
|
||||||
|
|
173
rslib/src/import_export/package/apkg/import/decks.rs
Normal file
173
rslib/src/import_export/package/apkg/import/decks.rs
Normal file
|
@ -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<String>,
|
||||||
|
renamed_parents: Vec<(String, String)>,
|
||||||
|
imported_decks: HashMap<DeckId, DeckId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<HashMap<DeckId, DeckId>> {
|
||||||
|
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<DeckConfig>) -> 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<Deck>) -> 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<String> {
|
||||||
|
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<Option<Deck>> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
mod cards;
|
mod cards;
|
||||||
|
mod decks;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -18,7 +19,6 @@ use zip::ZipArchive;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::CollectionBuilder,
|
collection::CollectionBuilder,
|
||||||
decks::NormalDeck,
|
|
||||||
import_export::{
|
import_export::{
|
||||||
gather::ExchangeData,
|
gather::ExchangeData,
|
||||||
package::{
|
package::{
|
||||||
|
@ -42,7 +42,6 @@ struct Context<'a> {
|
||||||
remapped_notetypes: HashMap<NotetypeId, NotetypeId>,
|
remapped_notetypes: HashMap<NotetypeId, NotetypeId>,
|
||||||
imported_notes: HashMap<NoteId, NoteId>,
|
imported_notes: HashMap<NoteId, NoteId>,
|
||||||
existing_notes: HashSet<NoteId>,
|
existing_notes: HashSet<NoteId>,
|
||||||
remapped_decks: HashMap<DeckId, DeckId>,
|
|
||||||
data: ExchangeData,
|
data: ExchangeData,
|
||||||
usn: Usn,
|
usn: Usn,
|
||||||
/// Map of source media files, that do not already exist in the target.
|
/// 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(),
|
remapped_notetypes: HashMap::new(),
|
||||||
imported_notes: HashMap::new(),
|
imported_notes: HashMap::new(),
|
||||||
existing_notes,
|
existing_notes,
|
||||||
remapped_decks: HashMap::new(),
|
|
||||||
used_media_entries: HashMap::new(),
|
used_media_entries: HashMap::new(),
|
||||||
normalize_notes,
|
normalize_notes,
|
||||||
})
|
})
|
||||||
|
@ -148,9 +146,8 @@ impl<'a> Context<'a> {
|
||||||
self.prepare_media()?;
|
self.prepare_media()?;
|
||||||
self.import_notetypes()?;
|
self.import_notetypes()?;
|
||||||
self.import_notes()?;
|
self.import_notes()?;
|
||||||
self.import_deck_configs()?;
|
let imported_decks = self.import_decks_and_configs()?;
|
||||||
self.import_decks()?;
|
self.import_cards_and_revlog(&imported_decks)?;
|
||||||
self.import_cards_and_revlog()?;
|
|
||||||
self.copy_media()
|
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<Option<Deck>> {
|
|
||||||
self.target_col
|
|
||||||
.storage
|
|
||||||
.get_deck_by_name(deck.name.as_native_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_media(&mut self) -> Result<()> {
|
fn copy_media(&mut self) -> Result<()> {
|
||||||
for (used, entry) in self.used_media_entries.values() {
|
for (used, entry) in self.used_media_entries.values() {
|
||||||
if *used {
|
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<String> {
|
|
||||||
renamed_parents.iter().find_map(|(old_parent, new_parent)| {
|
|
||||||
name.starts_with(old_parent)
|
|
||||||
.then(|| name.replacen(old_parent, new_parent, 1))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Notetype {
|
impl Notetype {
|
||||||
fn schema_hash(&self) -> [u8; 20] {
|
fn schema_hash(&self) -> [u8; 20] {
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
|
|
Loading…
Reference in a new issue