From 4d1cedc8b237352bb4ca6b4a40bdf082c045599c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 20 Apr 2021 09:00:25 +1000 Subject: [PATCH] implement deck config updating --- rslib/src/backend/deckconfig.rs | 2 +- rslib/src/deckconf/mod.rs | 70 +++++++++++++++++++----- rslib/src/deckconf/undo.rs | 22 ++------ rslib/src/deckconf/update.rs | 35 +++++++++++- rslib/src/decks/{add.rs => addupdate.rs} | 58 ++++++++++---------- rslib/src/decks/mod.rs | 28 +++++----- ts/deckconfig/lib.ts | 8 ++- 7 files changed, 144 insertions(+), 79 deletions(-) rename rslib/src/decks/{add.rs => addupdate.rs} (98%) diff --git a/rslib/src/backend/deckconfig.rs b/rslib/src/backend/deckconfig.rs index 968c10f70..debce059b 100644 --- a/rslib/src/backend/deckconfig.rs +++ b/rslib/src/backend/deckconfig.rs @@ -58,7 +58,7 @@ impl DeckConfigService for Backend { } fn remove_deck_config(&self, input: pb::DeckConfigId) -> Result { - self.with_col(|col| col.transact_no_undo(|col| col.remove_deck_config(input.into()))) + self.with_col(|col| col.transact_no_undo(|col| col.remove_deck_config_inner(input.into()))) .map(Into::into) } diff --git a/rslib/src/deckconf/mod.rs b/rslib/src/deckconf/mod.rs index 97fe27095..27cbb3b06 100644 --- a/rslib/src/deckconf/mod.rs +++ b/rslib/src/deckconf/mod.rs @@ -76,6 +76,13 @@ impl Default for DeckConf { } } +impl DeckConf { + pub(crate) fn set_modified(&mut self, usn: Usn) { + self.mtime_secs = TimestampSecs::now(); + self.usn = usn; + } +} + impl Collection { /// If fallback is true, guaranteed to return a deck config. pub fn get_deck_config(&self, dcid: DeckConfId, fallback: bool) -> Result> { @@ -92,29 +99,64 @@ impl Collection { Ok(None) } } +} +impl Collection { pub(crate) fn add_or_update_deck_config( - &self, - conf: &mut DeckConf, + &mut self, + config: &mut DeckConf, preserve_usn_and_mtime: bool, ) -> Result<()> { - if !preserve_usn_and_mtime { - conf.mtime_secs = TimestampSecs::now(); - conf.usn = self.usn()?; - } - let orig = self.storage.get_deck_config(conf.id)?; - if let Some(_orig) = orig { - self.storage.update_deck_conf(&conf) + let usn = if preserve_usn_and_mtime { + None } else { - if conf.id.0 == 0 { - conf.id.0 = TimestampMillis::now().0; - } - self.storage.add_deck_conf(conf) + Some(self.usn()?) + }; + + if config.id.0 == 0 { + self.add_deck_config_inner(config, usn) + } else { + let original = self + .storage + .get_deck_config(config.id)? + .ok_or(AnkiError::NotFound)?; + self.update_deck_config_inner(config, original, usn) } } + /// Assigns an id and adds to DB. If usn is provided, modification time and + /// usn will be updated. + pub(crate) fn add_deck_config_inner( + &mut self, + config: &mut DeckConf, + usn: Option, + ) -> Result<()> { + if let Some(usn) = usn { + config.set_modified(usn); + } + config.id.0 = TimestampMillis::now().0; + self.add_deck_config_undoable(config) + } + + /// Update an existing deck config. If usn is provided, modification time + /// and usn will be updated. + pub(crate) fn update_deck_config_inner( + &mut self, + config: &mut DeckConf, + original: DeckConf, + usn: Option, + ) -> Result<()> { + if config == &original { + return Ok(()); + } + if let Some(usn) = usn { + config.set_modified(usn); + } + self.update_deck_config_undoable(config, original) + } + /// Remove a deck configuration. This will force a full sync. - pub(crate) fn remove_deck_config(&mut self, dcid: DeckConfId) -> Result<()> { + pub(crate) fn remove_deck_config_inner(&mut self, dcid: DeckConfId) -> Result<()> { if dcid.0 == 1 { return Err(AnkiError::invalid_input("can't delete default conf")); } diff --git a/rslib/src/deckconf/undo.rs b/rslib/src/deckconf/undo.rs index 02a368129..6612524b9 100644 --- a/rslib/src/deckconf/undo.rs +++ b/rslib/src/deckconf/undo.rs @@ -11,8 +11,6 @@ pub(crate) enum UndoableDeckConfigChange { Removed(Box), } -// fixme: schema mod - impl Collection { pub(crate) fn undo_deck_config_change( &mut self, @@ -20,12 +18,12 @@ impl Collection { ) -> Result<()> { match change { UndoableDeckConfigChange::Added(config) => self.remove_deck_config_undoable(*config), - UndoableDeckConfigChange::Updated(mut config) => { + UndoableDeckConfigChange::Updated(config) => { let current = self .storage .get_deck_config(config.id)? .ok_or_else(|| AnkiError::invalid_input("deck config disappeared"))?; - self.update_single_deck_config_undoable(&mut *config, current) + self.update_deck_config_undoable(&config, current) } UndoableDeckConfigChange::Removed(config) => self.restore_deleted_deck_config(*config), } @@ -37,7 +35,6 @@ impl Collection { Ok(()) } - #[allow(dead_code)] pub(super) fn add_deck_config_undoable( &mut self, config: &mut DeckConf, @@ -47,26 +44,15 @@ impl Collection { Ok(()) } - pub(super) fn update_single_deck_config_undoable( + pub(super) fn update_deck_config_undoable( &mut self, - config: &mut DeckConf, + config: &DeckConf, original: DeckConf, ) -> Result<()> { self.save_undo(UndoableDeckConfigChange::Updated(Box::new(original))); self.storage.update_deck_conf(config) } - #[allow(dead_code)] - fn add_or_update_deck_config_with_existing_id_undoable( - &mut self, - config: &mut DeckConf, - ) -> Result<(), AnkiError> { - self.storage - .add_or_update_deck_config_with_existing_id(config)?; - self.save_undo(UndoableDeckConfigChange::Added(Box::new(config.clone()))); - Ok(()) - } - fn restore_deleted_deck_config(&mut self, config: DeckConf) -> Result<()> { self.storage .add_or_update_deck_config_with_existing_id(&config)?; diff --git a/rslib/src/deckconf/update.rs b/rslib/src/deckconf/update.rs index aa5c40d50..4ed942ba1 100644 --- a/rslib/src/deckconf/update.rs +++ b/rslib/src/deckconf/update.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 +//! Updating configs in bulk, from the deck config screen. + use std::collections::{HashMap, HashSet}; use crate::{ @@ -98,15 +100,42 @@ impl Collection { .collect()) } - fn update_deck_configs_inner(&mut self, input: UpdateDeckConfigsIn) -> Result<()> { + fn update_deck_configs_inner(&mut self, mut input: UpdateDeckConfigsIn) -> Result<()> { if input.configs.is_empty() { return Err(AnkiError::invalid_input("config not provided")); } + // handle removals first for dcid in &input.removed_config_ids { - self.remove_deck_config(*dcid)?; + self.remove_deck_config_inner(*dcid)?; } - todo!(); + // add/update provided configs + for conf in &mut input.configs { + self.add_or_update_deck_config(conf, false)?; + } + + // point current deck to last config + let usn = self.usn()?; + let config_id = input.configs.last().unwrap().id; + let mut deck = self + .storage + .get_deck(input.target_deck_id)? + .ok_or(AnkiError::NotFound)?; + let original = deck.clone(); + deck.normal_mut()?.config_id = config_id.0; + self.update_deck_inner(&mut deck, original, usn)?; + + if input.apply_to_children { + for mut child_deck in self.storage.child_decks(&deck)? { + let child_original = child_deck.clone(); + if let Ok(normal) = child_deck.normal_mut() { + normal.config_id = config_id.0; + self.update_deck_inner(&mut child_deck, child_original, usn)?; + } + } + } + + Ok(()) } } diff --git a/rslib/src/decks/add.rs b/rslib/src/decks/addupdate.rs similarity index 98% rename from rslib/src/decks/add.rs rename to rslib/src/decks/addupdate.rs index ca723f5b7..89ebd9331 100644 --- a/rslib/src/decks/add.rs +++ b/rslib/src/decks/addupdate.rs @@ -1,8 +1,37 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +//! Adding and updating. + use super::name::immediate_parent_name; use crate::{error::FilteredDeckError, prelude::*}; +impl Collection { + /// Add a new deck. The id must be 0, as it will be automatically assigned. + pub fn add_deck(&mut self, deck: &mut Deck) -> Result> { + self.transact(Op::AddDeck, |col| col.add_deck_inner(deck, col.usn()?)) + } + + pub fn update_deck(&mut self, deck: &mut Deck) -> Result> { + self.transact(Op::UpdateDeck, |col| { + let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?; + col.update_deck_inner(deck, existing_deck, col.usn()?) + }) + } + + /// Add or update an existing deck modified by the user. May add parents, + /// or rename children as required. Prefer add_deck() or update_deck() to + /// be explicit about your intentions; this function mainly exists so we + /// can integrate with older Python code that behaved this way. + pub fn add_or_update_deck(&mut self, deck: &mut Deck) -> Result> { + if deck.id.0 == 0 { + self.add_deck(deck) + } else { + self.update_deck(deck) + } + } +} + impl Collection { /// Rename deck if not unique. Bumps mtime and usn if /// name was changed, but otherwise leaves it the same. @@ -13,41 +42,16 @@ impl Collection { self.ensure_deck_name_unique(deck, usn) } - /// Add or update an existing deck modified by the user. May add parents, - /// or rename children as required. Prefer add_deck() or update_deck() to - /// be explicit about your intentions; this function mainly exists so we - /// can integrate with older Python code that behaved this way. - pub(crate) fn add_or_update_deck(&mut self, deck: &mut Deck) -> Result> { - if deck.id.0 == 0 { - self.add_deck(deck) - } else { - self.update_deck(deck) - } - } - - /// Add a new deck. The id must be 0, as it will be automatically assigned. - pub fn add_deck(&mut self, deck: &mut Deck) -> Result> { + pub(crate) fn add_deck_inner(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> { if deck.id.0 != 0 { return Err(AnkiError::invalid_input("deck to add must have id 0")); } - - self.transact(Op::AddDeck, |col| col.add_deck_inner(deck, col.usn()?)) - } - - pub(crate) fn add_deck_inner(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> { self.prepare_deck_for_update(deck, usn)?; deck.set_modified(usn); self.match_or_create_parents(deck, usn)?; self.add_deck_undoable(deck) } - pub fn update_deck(&mut self, deck: &mut Deck) -> Result> { - self.transact(Op::UpdateDeck, |col| { - let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?; - col.update_deck_inner(deck, existing_deck, col.usn()?) - }) - } - pub(crate) fn update_deck_inner( &mut self, deck: &mut Deck, @@ -91,9 +95,7 @@ impl Collection { deck.set_modified(usn); self.add_or_update_single_deck_with_existing_id(&mut deck, usn) } -} -impl Collection { /// Add a single, normal deck with the provided name for a child deck. /// Caller must have done necessarily validation on name. fn add_parent_deck(&mut self, machine_name: &str, usn: Usn) -> Result<()> { diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index e4e3b8f4e..f847f1a5a 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -mod add; +mod addupdate; mod counts; mod current; mod filtered; @@ -134,6 +134,20 @@ impl Deck { } } +impl Collection { + pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result { + let name = NativeDeckName::from_human_name(human_name); + if let Some(did) = self.storage.get_deck_id(name.as_native_str())? { + self.storage.get_deck(did).map(|opt| opt.unwrap()) + } else { + let mut deck = Deck::new_normal(); + deck.name = name; + self.add_or_update_deck(&mut deck)?; + Ok(deck) + } + } +} + impl Collection { pub(crate) fn get_deck(&mut self, did: DeckId) -> Result>> { if let Some(deck) = self.state.deck_cache.get(&did) { @@ -152,18 +166,6 @@ impl Collection { self.storage.deck_is_empty(DeckId(1)) } - pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result { - let name = NativeDeckName::from_human_name(human_name); - if let Some(did) = self.storage.get_deck_id(name.as_native_str())? { - self.storage.get_deck(did).map(|opt| opt.unwrap()) - } else { - let mut deck = Deck::new_normal(); - deck.name = name; - self.add_or_update_deck(&mut deck)?; - Ok(deck) - } - } - /// Get a deck based on its human name. If you have a machine name, /// use the method in storage instead. pub(crate) fn get_deck_id(&self, human_name: &str) -> Result> { diff --git a/ts/deckconfig/lib.ts b/ts/deckconfig/lib.ts index e5e826a4f..045dcf11a 100644 --- a/ts/deckconfig/lib.ts +++ b/ts/deckconfig/lib.ts @@ -51,6 +51,7 @@ export class DeckConfigState { private selectedIdx: number; private configListSetter!: (val: ConfigListEntry[]) => void; private parentLimitsSetter!: (val: ParentLimits) => void; + private modifiedConfigs: Set = new Set(); private removedConfigs: DeckConfigId[] = []; private schemaModified: boolean; @@ -107,8 +108,11 @@ export class DeckConfigState { return; } const uniqueName = this.ensureNewNameUnique(name); - this.configs[this.selectedIdx].config.name = uniqueName; - this.configs[this.selectedIdx].config.mtimeSecs = 0; + const config = this.configs[this.selectedIdx].config; + config.name = uniqueName; + if (config.id) { + this.modifiedConfigs.add(config.id); + } this.updateConfigList(); }