From 96940f0527c3f073f61475ce717fc11a927b2b3a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 9 Mar 2021 10:58:55 +1000 Subject: [PATCH] undo support for config entries --- rslib/src/collection.rs | 2 +- rslib/src/config/bool.rs | 2 +- rslib/src/config/deck.rs | 6 +- rslib/src/config/mod.rs | 60 +++++++++---- rslib/src/config/notetype.rs | 4 +- rslib/src/config/string.rs | 2 +- rslib/src/config/undo.rs | 111 +++++++++++++++++++++++++ rslib/src/dbcheck.rs | 2 +- rslib/src/notetype/cardgen.rs | 7 +- rslib/src/notetype/stock.rs | 15 ++-- rslib/src/scheduler/answering/mod.rs | 2 +- rslib/src/scheduler/mod.rs | 8 +- rslib/src/stats/card.rs | 2 +- rslib/src/stats/graphs.rs | 4 +- rslib/src/stats/today.rs | 7 +- rslib/src/storage/config/get_entry.sql | 5 ++ rslib/src/storage/config/mod.rs | 38 ++++++--- rslib/src/sync/mod.rs | 4 +- rslib/src/undo/changes.rs | 15 +++- 19 files changed, 237 insertions(+), 59 deletions(-) create mode 100644 rslib/src/config/undo.rs create mode 100644 rslib/src/storage/config/get_entry.sql diff --git a/rslib/src/collection.rs b/rslib/src/collection.rs index 90db7fd8a..bb701f9b6 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection.rs @@ -44,7 +44,7 @@ pub fn open_collection>( #[cfg(test)] pub fn open_test_collection() -> Collection { use crate::config::SchedulerVersion; - let col = open_test_collection_with_server(false); + let mut col = open_test_collection_with_server(false); // our unit tests assume v2 is the default, but at the time of writing v1 // is still the default col.set_scheduler_version_config_key(SchedulerVersion::V2) diff --git a/rslib/src/config/bool.rs b/rslib/src/config/bool.rs index 150eeaacb..06f13e31e 100644 --- a/rslib/src/config/bool.rs +++ b/rslib/src/config/bool.rs @@ -61,7 +61,7 @@ impl Collection { } } - pub(crate) fn set_bool(&self, key: BoolKey, value: bool) -> Result<()> { + pub(crate) fn set_bool(&mut self, key: BoolKey, value: bool) -> Result<()> { self.set_config(key, &value) } } diff --git a/rslib/src/config/deck.rs b/rslib/src/config/deck.rs index b11b7b5ca..22c7b8694 100644 --- a/rslib/src/config/deck.rs +++ b/rslib/src/config/deck.rs @@ -34,7 +34,11 @@ impl Collection { self.get_config_optional(key.as_str()) } - pub(crate) fn set_last_notetype_for_deck(&self, did: DeckID, ntid: NoteTypeID) -> Result<()> { + pub(crate) fn set_last_notetype_for_deck( + &mut self, + did: DeckID, + ntid: NoteTypeID, + ) -> Result<()> { let key = DeckConfigKey::LastNotetype.for_deck(did); self.set_config(key.as_str(), &ntid) } diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index a70553234..12c0ced95 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -6,6 +6,7 @@ mod deck; mod notetype; pub(crate) mod schema11; mod string; +pub(crate) mod undo; pub use self::{bool::BoolKey, string::StringKey}; use crate::prelude::*; @@ -15,6 +16,26 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use slog::warn; use strum::IntoStaticStr; +/// Only used when updating/undoing. +#[derive(Debug)] +pub(crate) struct ConfigEntry { + pub key: String, + pub value: Vec, + pub usn: Usn, + pub mtime: TimestampSecs, +} + +impl ConfigEntry { + pub(crate) fn boxed(key: &str, value: Vec, usn: Usn, mtime: TimestampSecs) -> Box { + Box::new(Self { + key: key.into(), + value, + usn, + mtime, + }) + } +} + #[derive(IntoStaticStr)] #[strum(serialize_all = "camelCase")] pub(crate) enum ConfigKey { @@ -76,19 +97,24 @@ impl Collection { self.get_config_optional(key).unwrap_or_default() } - pub(crate) fn set_config<'a, T: Serialize, K>(&self, key: K, val: &T) -> Result<()> + pub(crate) fn set_config<'a, T: Serialize, K>(&mut self, key: K, val: &T) -> Result<()> where K: Into<&'a str>, { - self.storage - .set_config_value(key.into(), val, self.usn()?, TimestampSecs::now()) + let entry = ConfigEntry::boxed( + key.into(), + serde_json::to_vec(val)?, + self.usn()?, + TimestampSecs::now(), + ); + self.set_config_undoable(entry) } - pub(crate) fn remove_config<'a, K>(&self, key: K) -> Result<()> + pub(crate) fn remove_config<'a, K>(&mut self, key: K) -> Result<()> where K: Into<&'a str>, { - self.storage.remove_config(key.into()) + self.remove_config_undoable(key.into()) } /// Remove all keys starting with provided prefix, which must end with '_'. @@ -107,7 +133,7 @@ impl Collection { self.get_config_optional(ConfigKey::CreationOffset) } - pub(crate) fn set_creation_utc_offset(&self, mins: Option) -> Result<()> { + pub(crate) fn set_creation_utc_offset(&mut self, mins: Option) -> Result<()> { if let Some(mins) = mins { self.set_config(ConfigKey::CreationOffset, &mins) } else { @@ -119,7 +145,7 @@ impl Collection { self.get_config_optional(ConfigKey::LocalOffset) } - pub(crate) fn set_configured_utc_offset(&self, mins: i32) -> Result<()> { + pub(crate) fn set_configured_utc_offset(&mut self, mins: i32) -> Result<()> { self.set_config(ConfigKey::LocalOffset, &mins) } @@ -128,7 +154,7 @@ impl Collection { .map(|r| r.min(23)) } - pub(crate) fn set_v2_rollover(&self, hour: u32) -> Result<()> { + pub(crate) fn set_v2_rollover(&mut self, hour: u32) -> Result<()> { self.set_config(ConfigKey::Rollover, &hour) } @@ -136,7 +162,7 @@ impl Collection { self.get_config_default(ConfigKey::NextNewCardPosition) } - pub(crate) fn get_and_update_next_card_position(&self) -> Result { + pub(crate) fn get_and_update_next_card_position(&mut self) -> Result { let pos: u32 = self .get_config_optional(ConfigKey::NextNewCardPosition) .unwrap_or_default(); @@ -144,7 +170,7 @@ impl Collection { Ok(pos) } - pub(crate) fn set_next_card_position(&self, pos: u32) -> Result<()> { + pub(crate) fn set_next_card_position(&mut self, pos: u32) -> Result<()> { self.set_config(ConfigKey::NextNewCardPosition, &pos) } @@ -154,7 +180,7 @@ impl Collection { } /// Caution: this only updates the config setting. - pub(crate) fn set_scheduler_version_config_key(&self, ver: SchedulerVersion) -> Result<()> { + pub(crate) fn set_scheduler_version_config_key(&mut self, ver: SchedulerVersion) -> Result<()> { self.set_config(ConfigKey::SchedulerVersion, &ver) } @@ -163,7 +189,7 @@ impl Collection { .unwrap_or(1200) } - pub(crate) fn set_learn_ahead_secs(&self, secs: u32) -> Result<()> { + pub(crate) fn set_learn_ahead_secs(&mut self, secs: u32) -> Result<()> { self.set_config(ConfigKey::LearnAheadSecs, &secs) } @@ -175,7 +201,7 @@ impl Collection { } } - pub(crate) fn set_new_review_mix(&self, mix: NewReviewMix) -> Result<()> { + pub(crate) fn set_new_review_mix(&mut self, mix: NewReviewMix) -> Result<()> { self.set_config(ConfigKey::NewReviewMix, &(mix as u8)) } @@ -184,7 +210,7 @@ impl Collection { .unwrap_or(Weekday::Sunday) } - pub(crate) fn set_first_day_of_week(&self, weekday: Weekday) -> Result<()> { + pub(crate) fn set_first_day_of_week(&mut self, weekday: Weekday) -> Result<()> { self.set_config(ConfigKey::FirstDayOfWeek, &weekday) } @@ -193,7 +219,7 @@ impl Collection { .unwrap_or_default() } - pub(crate) fn set_answer_time_limit_secs(&self, secs: u32) -> Result<()> { + pub(crate) fn set_answer_time_limit_secs(&mut self, secs: u32) -> Result<()> { self.set_config(ConfigKey::AnswerTimeLimitSecs, &secs) } @@ -202,7 +228,7 @@ impl Collection { .unwrap_or_default() } - pub(crate) fn set_last_unburied_day(&self, day: u32) -> Result<()> { + pub(crate) fn set_last_unburied_day(&mut self, day: u32) -> Result<()> { self.set_config(ConfigKey::LastUnburiedDay, &day) } } @@ -274,7 +300,7 @@ mod test { #[test] fn get_set() { - let col = open_test_collection(); + let mut col = open_test_collection(); // missing key assert_eq!(col.get_config_optional::, _>("test"), None); diff --git a/rslib/src/config/notetype.rs b/rslib/src/config/notetype.rs index 42b7d1e50..acda970da 100644 --- a/rslib/src/config/notetype.rs +++ b/rslib/src/config/notetype.rs @@ -28,7 +28,7 @@ impl Collection { self.get_config_optional(ConfigKey::CurrentNoteTypeID) } - pub(crate) fn set_current_notetype_id(&self, ntid: NoteTypeID) -> Result<()> { + pub(crate) fn set_current_notetype_id(&mut self, ntid: NoteTypeID) -> Result<()> { self.set_config(ConfigKey::CurrentNoteTypeID, &ntid) } @@ -41,7 +41,7 @@ impl Collection { self.get_config_optional(key.as_str()) } - pub(crate) fn set_last_deck_for_notetype(&self, id: NoteTypeID, did: DeckID) -> Result<()> { + pub(crate) fn set_last_deck_for_notetype(&mut self, id: NoteTypeID, did: DeckID) -> Result<()> { let key = NoteTypeConfigKey::LastDeckAddedTo.for_notetype(id); self.set_config(key.as_str(), &did) } diff --git a/rslib/src/config/string.rs b/rslib/src/config/string.rs index d39f371d8..d5aa918f8 100644 --- a/rslib/src/config/string.rs +++ b/rslib/src/config/string.rs @@ -22,7 +22,7 @@ impl Collection { .unwrap_or_else(|| default.to_string()) } - pub(crate) fn set_string(&self, key: StringKey, val: &str) -> Result<()> { + pub(crate) fn set_string(&mut self, key: StringKey, val: &str) -> Result<()> { self.set_config(key, &val) } } diff --git a/rslib/src/config/undo.rs b/rslib/src/config/undo.rs new file mode 100644 index 000000000..1aec6d075 --- /dev/null +++ b/rslib/src/config/undo.rs @@ -0,0 +1,111 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::ConfigEntry; +use crate::prelude::*; + +#[derive(Debug)] +pub(crate) enum UndoableConfigChange { + Added(Box), + Updated(Box), + Removed(Box), +} + +impl Collection { + pub(crate) fn undo_config_change(&mut self, change: UndoableConfigChange) -> Result<()> { + match change { + UndoableConfigChange::Added(entry) => self.remove_config_undoable(&entry.key), + UndoableConfigChange::Updated(entry) => { + let current = self + .storage + .get_config_entry(&entry.key)? + .ok_or_else(|| AnkiError::invalid_input("config disappeared"))?; + self.update_config_entry_undoable(entry, current) + } + UndoableConfigChange::Removed(entry) => self.add_config_entry_undoable(entry), + } + } + + pub(super) fn set_config_undoable(&mut self, entry: Box) -> Result<()> { + if let Some(original) = self.storage.get_config_entry(&entry.key)? { + self.update_config_entry_undoable(entry, original) + } else { + self.add_config_entry_undoable(entry) + } + } + + pub(super) fn remove_config_undoable(&mut self, key: &str) -> Result<()> { + if let Some(current) = self.storage.get_config_entry(key)? { + self.save_undo(UndoableConfigChange::Removed(current)); + self.storage.remove_config(key)?; + } + + Ok(()) + } + + fn add_config_entry_undoable(&mut self, entry: Box) -> Result<()> { + self.storage.set_config_entry(&entry)?; + self.save_undo(UndoableConfigChange::Added(entry)); + Ok(()) + } + + fn update_config_entry_undoable( + &mut self, + entry: Box, + original: Box, + ) -> Result<()> { + if entry.value != original.value { + self.save_undo(UndoableConfigChange::Updated(original)); + self.storage.set_config_entry(&entry)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::collection::open_test_collection; + + #[test] + fn undo() -> Result<()> { + let mut col = open_test_collection(); + // the op kind doesn't matter, we just need undo enabled + let op = Some(UndoableOpKind::Bury); + // test key + let key = BoolKey::NormalizeNoteText; + + // not set by default, but defaults to true + assert_eq!(col.get_bool(key), true); + + // first set adds the key + col.transact(op, |col| col.set_bool(key, false))?; + assert_eq!(col.get_bool(key), false); + + // mutate it twice + col.transact(op, |col| col.set_bool(key, true))?; + assert_eq!(col.get_bool(key), true); + col.transact(op, |col| col.set_bool(key, false))?; + assert_eq!(col.get_bool(key), false); + + // when we remove it, it goes back to its default + col.transact(op, |col| col.remove_config(key))?; + assert_eq!(col.get_bool(key), true); + + // undo the removal + col.undo()?; + assert_eq!(col.get_bool(key), false); + + // undo the mutations + col.undo()?; + assert_eq!(col.get_bool(key), true); + col.undo()?; + assert_eq!(col.get_bool(key), false); + + // and undo the initial add + col.undo()?; + assert_eq!(col.get_bool(key), true); + + Ok(()) + } +} diff --git a/rslib/src/dbcheck.rs b/rslib/src/dbcheck.rs index e6c5e50d6..d5668d3a0 100644 --- a/rslib/src/dbcheck.rs +++ b/rslib/src/dbcheck.rs @@ -409,7 +409,7 @@ impl Collection { Ok(()) } - fn update_next_new_position(&self) -> Result<()> { + fn update_next_new_position(&mut self) -> Result<()> { let pos = self.storage.max_new_card_position().unwrap_or(0); self.set_next_card_position(pos) } diff --git a/rslib/src/notetype/cardgen.rs b/rslib/src/notetype/cardgen.rs index 2e1993794..e89e37ffa 100644 --- a/rslib/src/notetype/cardgen.rs +++ b/rslib/src/notetype/cardgen.rs @@ -299,7 +299,12 @@ impl Collection { // not sure if entry() can be used due to get_deck_config() returning a result #[allow(clippy::map_entry)] - fn due_for_deck(&self, did: DeckID, dcid: DeckConfID, cache: &mut CardGenCache) -> Result { + fn due_for_deck( + &mut self, + did: DeckID, + dcid: DeckConfID, + cache: &mut CardGenCache, + ) -> Result { if !cache.deck_configs.contains_key(&did) { let conf = self.get_deck_config(dcid, true)?.unwrap(); cache.deck_configs.insert(did, conf); diff --git a/rslib/src/notetype/stock.rs b/rslib/src/notetype/stock.rs index b124a398b..420cc1b5f 100644 --- a/rslib/src/notetype/stock.rs +++ b/rslib/src/notetype/stock.rs @@ -3,8 +3,13 @@ use super::NoteTypeKind; use crate::{ - config::ConfigKey, err::Result, i18n::I18n, i18n::TR, notetype::NoteType, - storage::SqliteStorage, timestamp::TimestampSecs, + config::{ConfigEntry, ConfigKey}, + err::Result, + i18n::I18n, + i18n::TR, + notetype::NoteType, + storage::SqliteStorage, + timestamp::TimestampSecs, }; use crate::backend_proto::stock_note_type::Kind; @@ -14,12 +19,12 @@ impl SqliteStorage { for (idx, mut nt) in all_stock_notetypes(i18n).into_iter().enumerate() { self.add_new_notetype(&mut nt)?; if idx == Kind::Basic as usize { - self.set_config_value( + self.set_config_entry(&ConfigEntry::boxed( ConfigKey::CurrentNoteTypeID.into(), - &nt.id, + serde_json::to_vec(&nt.id)?, self.usn(false)?, TimestampSecs::now(), - )?; + ))?; } } Ok(()) diff --git a/rslib/src/scheduler/answering/mod.rs b/rslib/src/scheduler/answering/mod.rs index c4e80466a..3913bee97 100644 --- a/rslib/src/scheduler/answering/mod.rs +++ b/rslib/src/scheduler/answering/mod.rs @@ -193,7 +193,7 @@ impl Collection { } /// Describe the next intervals, to display on the answer buttons. - pub fn describe_next_states(&self, choices: NextCardStates) -> Result> { + pub fn describe_next_states(&mut self, choices: NextCardStates) -> Result> { let collapse_time = self.learn_ahead_secs(); let now = TimestampSecs::now(); let timing = self.timing_for_timestamp(now)?; diff --git a/rslib/src/scheduler/mod.rs b/rslib/src/scheduler/mod.rs index c5e678d43..01b1cda82 100644 --- a/rslib/src/scheduler/mod.rs +++ b/rslib/src/scheduler/mod.rs @@ -23,7 +23,7 @@ use timing::{ }; impl Collection { - pub fn timing_today(&self) -> Result { + pub fn timing_today(&mut self) -> Result { self.timing_for_timestamp(TimestampSecs::now()) } @@ -31,7 +31,7 @@ impl Collection { Ok(((self.timing_today()?.days_elapsed as i32) + delta).max(0) as u32) } - pub(crate) fn timing_for_timestamp(&self, now: TimestampSecs) -> Result { + pub(crate) fn timing_for_timestamp(&mut self, now: TimestampSecs) -> Result { let current_utc_offset = self.local_utc_offset_for_user()?; let rollover_hour = match self.scheduler_version() { @@ -63,7 +63,7 @@ impl Collection { /// ensuring the config reflects the current value. /// In the server case, return the value set in the config, and /// fall back on UTC if it's missing/invalid. - pub(crate) fn local_utc_offset_for_user(&self) -> Result { + pub(crate) fn local_utc_offset_for_user(&mut self) -> Result { let config_tz = self .get_configured_utc_offset() .and_then(|v| FixedOffset::west_opt(v * 60)) @@ -99,7 +99,7 @@ impl Collection { } } - pub(crate) fn set_rollover_for_current_scheduler(&self, hour: u8) -> Result<()> { + pub(crate) fn set_rollover_for_current_scheduler(&mut self, hour: u8) -> Result<()> { match self.scheduler_version() { SchedulerVersion::V1 => { self.storage diff --git a/rslib/src/stats/card.rs b/rslib/src/stats/card.rs index 549613e4a..f6d58e544 100644 --- a/rslib/src/stats/card.rs +++ b/rslib/src/stats/card.rs @@ -126,7 +126,7 @@ impl Collection { }) } - fn card_stats_to_string(&self, cs: CardStats) -> Result { + fn card_stats_to_string(&mut self, cs: CardStats) -> Result { let offset = self.local_utc_offset_for_user()?; let i18n = &self.i18n; diff --git a/rslib/src/stats/graphs.rs b/rslib/src/stats/graphs.rs index 084d3a4b2..fb376541c 100644 --- a/rslib/src/stats/graphs.rs +++ b/rslib/src/stats/graphs.rs @@ -20,7 +20,7 @@ impl Collection { self.graph_data(all, days) } - fn graph_data(&self, all: bool, days: u32) -> Result { + fn graph_data(&mut self, all: bool, days: u32) -> Result { let timing = self.timing_today()?; let revlog_start = TimestampSecs(if days > 0 { timing.next_day_at - (((days as i64) + 1) * 86_400) @@ -60,7 +60,7 @@ impl Collection { }) } - pub(crate) fn set_graph_preferences(&self, prefs: pb::GraphPreferences) -> Result<()> { + pub(crate) fn set_graph_preferences(&mut self, prefs: pb::GraphPreferences) -> Result<()> { self.set_first_day_of_week(match prefs.calendar_first_day_of_week { 1 => Weekday::Monday, 5 => Weekday::Friday, diff --git a/rslib/src/stats/today.rs b/rslib/src/stats/today.rs index 3886dddfe..0a75d04c4 100644 --- a/rslib/src/stats/today.rs +++ b/rslib/src/stats/today.rs @@ -18,10 +18,9 @@ pub fn studied_today(cards: u32, secs: f32, i18n: &I18n) -> String { } impl Collection { - pub fn studied_today(&self) -> Result { - let today = self - .storage - .studied_today(self.timing_today()?.next_day_at)?; + pub fn studied_today(&mut self) -> Result { + let timing = self.timing_today()?; + let today = self.storage.studied_today(timing.next_day_at)?; Ok(studied_today(today.cards, today.seconds as f32, &self.i18n)) } } diff --git a/rslib/src/storage/config/get_entry.sql b/rslib/src/storage/config/get_entry.sql new file mode 100644 index 000000000..be64ae323 --- /dev/null +++ b/rslib/src/storage/config/get_entry.sql @@ -0,0 +1,5 @@ +SELECT val, + usn, + mtime_secs +FROM config +WHERE KEY = ? \ No newline at end of file diff --git a/rslib/src/storage/config/mod.rs b/rslib/src/storage/config/mod.rs index 832377a4b..d404002e4 100644 --- a/rslib/src/storage/config/mod.rs +++ b/rslib/src/storage/config/mod.rs @@ -2,24 +2,17 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use super::SqliteStorage; -use crate::{err::Result, timestamp::TimestampSecs, types::Usn}; +use crate::{config::ConfigEntry, err::Result, timestamp::TimestampSecs, types::Usn}; use rusqlite::{params, NO_PARAMS}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; use serde_json::Value; use std::collections::HashMap; impl SqliteStorage { - pub(crate) fn set_config_value( - &self, - key: &str, - val: &T, - usn: Usn, - mtime: TimestampSecs, - ) -> Result<()> { - let json = serde_json::to_vec(val)?; + pub(crate) fn set_config_entry(&self, entry: &ConfigEntry) -> Result<()> { self.db .prepare_cached(include_str!("add.sql"))? - .execute(params![key, usn, mtime, &json])?; + .execute(params![&entry.key, entry.usn, entry.mtime, &entry.value])?; Ok(()) } @@ -41,6 +34,22 @@ impl SqliteStorage { .transpose() } + /// Return the raw bytes and other metadata, for undoing. + pub(crate) fn get_config_entry(&self, key: &str) -> Result>> { + self.db + .prepare_cached(include_str!("get_entry.sql"))? + .query_and_then(&[key], |row| { + Ok(ConfigEntry::boxed( + key, + row.get(0)?, + row.get(1)?, + row.get(2)?, + )) + })? + .next() + .transpose() + } + /// Prefix is expected to end with '_'. pub(crate) fn get_config_prefix(&self, prefix: &str) -> Result)>> { let mut end = prefix.to_string(); @@ -70,7 +79,12 @@ impl SqliteStorage { ) -> Result<()> { self.db.execute("delete from config", NO_PARAMS)?; for (key, val) in conf.iter() { - self.set_config_value(key, val, usn, mtime)?; + self.set_config_entry(&ConfigEntry::boxed( + key, + serde_json::to_vec(&val)?, + usn, + mtime, + ))?; } Ok(()) } diff --git a/rslib/src/sync/mod.rs b/rslib/src/sync/mod.rs index 7f43a9c8f..81115e923 100644 --- a/rslib/src/sync/mod.rs +++ b/rslib/src/sync/mod.rs @@ -328,8 +328,8 @@ where SyncActionRequired::NormalSyncRequired => { self.col.discard_undo_and_study_queues(); self.col.storage.begin_trx()?; - self.col - .unbury_if_day_rolled_over(self.col.timing_today()?)?; + let timing = self.col.timing_today()?; + self.col.unbury_if_day_rolled_over(timing)?; match self.normal_sync_inner(state).await { Ok(success) => { self.col.storage.commit_trx()?; diff --git a/rslib/src/undo/changes.rs b/rslib/src/undo/changes.rs index bad7efe28..f50eba795 100644 --- a/rslib/src/undo/changes.rs +++ b/rslib/src/undo/changes.rs @@ -2,9 +2,10 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::{ - card::undo::UndoableCardChange, decks::undo::UndoableDeckChange, - notes::undo::UndoableNoteChange, prelude::*, revlog::undo::UndoableRevlogChange, - scheduler::queue::undo::UndoableQueueChange, tags::undo::UndoableTagChange, + card::undo::UndoableCardChange, config::undo::UndoableConfigChange, + decks::undo::UndoableDeckChange, notes::undo::UndoableNoteChange, prelude::*, + revlog::undo::UndoableRevlogChange, scheduler::queue::undo::UndoableQueueChange, + tags::undo::UndoableTagChange, }; #[derive(Debug)] @@ -15,6 +16,7 @@ pub(crate) enum UndoableChange { Tag(UndoableTagChange), Revlog(UndoableRevlogChange), Queue(UndoableQueueChange), + Config(UndoableConfigChange), } impl UndoableChange { @@ -26,6 +28,7 @@ impl UndoableChange { UndoableChange::Tag(c) => col.undo_tag_change(c), UndoableChange::Revlog(c) => col.undo_revlog_change(c), UndoableChange::Queue(c) => col.undo_queue_change(c), + UndoableChange::Config(c) => col.undo_config_change(c), } } } @@ -65,3 +68,9 @@ impl From for UndoableChange { UndoableChange::Queue(c) } } + +impl From for UndoableChange { + fn from(c: UndoableConfigChange) -> Self { + UndoableChange::Config(c) + } +}