From 2a15ba5404f355ba69e9fb27639bba5350afb722 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Apr 2022 11:40:36 +0200 Subject: [PATCH] Try to keep source ids of imported notes --- .../src/import_export/package/apkg/import.rs | 20 +++++++++++--- rslib/src/notes/undo.rs | 10 +++++++ rslib/src/storage/note/add_if_unique.sql | 27 +++++++++++++++++++ rslib/src/storage/note/mod.rs | 25 +++++++++++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 rslib/src/storage/note/add_if_unique.sql diff --git a/rslib/src/import_export/package/apkg/import.rs b/rslib/src/import_export/package/apkg/import.rs index 1edd16168..8e0711acf 100644 --- a/rslib/src/import_export/package/apkg/import.rs +++ b/rslib/src/import_export/package/apkg/import.rs @@ -39,6 +39,7 @@ struct Context<'a> { guid_map: HashMap, remapped_notetypes: HashMap, remapped_notes: HashMap, + existing_notes: HashSet, remapped_decks: HashMap, remapped_deck_configs: HashMap, data: ExchangeData, @@ -128,6 +129,7 @@ impl<'a> Context<'a> { let guid_map = target_col.storage.note_guid_map()?; 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()?; Ok(Self { target_col, archive, @@ -137,6 +139,7 @@ impl<'a> Context<'a> { conflicting_notes: HashSet::new(), remapped_notetypes: HashMap::new(), remapped_notes: HashMap::new(), + existing_notes, remapped_decks: HashMap::new(), remapped_deck_configs: HashMap::new(), added_cards: HashSet::new(), @@ -248,12 +251,23 @@ impl<'a> Context<'a> { let notetype = self.get_expected_notetype(note.notetype_id)?; note.prepare_for_update(¬etype, self.normalize_notes)?; note.usn = self.usn; - let old_id = std::mem::take(&mut note.id); - self.target_col.add_note_only_undoable(note)?; - self.remapped_notes.insert(old_id, note.id); + self.uniquify_note_id(note); + + self.target_col.add_note_only_with_id_undoable(note)?; + self.existing_notes.insert(note.id); Ok(()) } + fn uniquify_note_id(&mut self, note: &mut Note) { + let original = note.id; + while self.existing_notes.contains(¬e.id) { + note.id.0 += 999; + } + if original != note.id { + self.remapped_notes.insert(original, note.id); + } + } + fn get_expected_notetype(&mut self, ntid: NotetypeId) -> Result> { self.target_col .get_notetype(ntid)? diff --git a/rslib/src/notes/undo.rs b/rslib/src/notes/undo.rs index 2022a1145..89e22dc48 100644 --- a/rslib/src/notes/undo.rs +++ b/rslib/src/notes/undo.rs @@ -90,6 +90,16 @@ impl Collection { Ok(()) } + /// Add a note, not adding any cards. Caller guarantees id is unique. + pub(crate) fn add_note_only_with_id_undoable(&mut self, note: &mut Note) -> Result<()> { + if self.storage.add_note_if_unique(note)? { + self.save_undo(UndoableNoteChange::Added(Box::new(note.clone()))); + Ok(()) + } else { + Err(AnkiError::invalid_input("note id existed")) + } + } + pub(crate) fn update_note_tags_undoable( &mut self, tags: &NoteTags, diff --git a/rslib/src/storage/note/add_if_unique.sql b/rslib/src/storage/note/add_if_unique.sql new file mode 100644 index 000000000..1dd408bbe --- /dev/null +++ b/rslib/src/storage/note/add_if_unique.sql @@ -0,0 +1,27 @@ +INSERT + OR IGNORE INTO notes ( + id, + guid, + mid, + mod, + usn, + tags, + flds, + sfld, + csum, + flags, + data + ) +VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + 0, + "" + ) \ No newline at end of file diff --git a/rslib/src/storage/note/mod.rs b/rslib/src/storage/note/mod.rs index 812e488ee..144cb3d12 100644 --- a/rslib/src/storage/note/mod.rs +++ b/rslib/src/storage/note/mod.rs @@ -42,6 +42,13 @@ impl super::SqliteStorage { .transpose() } + pub fn get_all_note_ids(&self) -> Result> { + self.db + .prepare("SELECT id FROM notes")? + .query_and_then([], |row| Ok(row.get(0)?))? + .collect() + } + /// If fields have been modified, caller must call note.prepare_for_update() prior to calling this. pub(crate) fn update_note(&self, note: &Note) -> Result<()> { assert!(note.id.0 != 0); @@ -78,6 +85,24 @@ impl super::SqliteStorage { Ok(()) } + pub(crate) fn add_note_if_unique(&self, note: &Note) -> Result { + self.db + .prepare_cached(include_str!("add_if_unique.sql"))? + .execute(params![ + note.id, + note.guid, + note.notetype_id, + note.mtime, + note.usn, + join_tags(¬e.tags), + join_fields(note.fields()), + note.sort_field.as_ref().unwrap(), + note.checksum.unwrap(), + ]) + .map(|added| added == 1) + .map_err(Into::into) + } + /// Add or update the provided note, preserving ID. Used by the syncing code. pub(crate) fn add_or_update_note(&self, note: &Note) -> Result<()> { let mut stmt = self.db.prepare_cached(include_str!("add_or_update.sql"))?;