undo support for config entries

This commit is contained in:
Damien Elmes 2021-03-09 10:58:55 +10:00
parent ce243c2cae
commit 96940f0527
19 changed files with 237 additions and 59 deletions

View file

@ -44,7 +44,7 @@ pub fn open_collection<P: Into<PathBuf>>(
#[cfg(test)] #[cfg(test)]
pub fn open_test_collection() -> Collection { pub fn open_test_collection() -> Collection {
use crate::config::SchedulerVersion; 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 // our unit tests assume v2 is the default, but at the time of writing v1
// is still the default // is still the default
col.set_scheduler_version_config_key(SchedulerVersion::V2) col.set_scheduler_version_config_key(SchedulerVersion::V2)

View file

@ -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) self.set_config(key, &value)
} }
} }

View file

@ -34,7 +34,11 @@ impl Collection {
self.get_config_optional(key.as_str()) 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); let key = DeckConfigKey::LastNotetype.for_deck(did);
self.set_config(key.as_str(), &ntid) self.set_config(key.as_str(), &ntid)
} }

View file

@ -6,6 +6,7 @@ mod deck;
mod notetype; mod notetype;
pub(crate) mod schema11; pub(crate) mod schema11;
mod string; mod string;
pub(crate) mod undo;
pub use self::{bool::BoolKey, string::StringKey}; pub use self::{bool::BoolKey, string::StringKey};
use crate::prelude::*; use crate::prelude::*;
@ -15,6 +16,26 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use slog::warn; use slog::warn;
use strum::IntoStaticStr; use strum::IntoStaticStr;
/// Only used when updating/undoing.
#[derive(Debug)]
pub(crate) struct ConfigEntry {
pub key: String,
pub value: Vec<u8>,
pub usn: Usn,
pub mtime: TimestampSecs,
}
impl ConfigEntry {
pub(crate) fn boxed(key: &str, value: Vec<u8>, usn: Usn, mtime: TimestampSecs) -> Box<Self> {
Box::new(Self {
key: key.into(),
value,
usn,
mtime,
})
}
}
#[derive(IntoStaticStr)] #[derive(IntoStaticStr)]
#[strum(serialize_all = "camelCase")] #[strum(serialize_all = "camelCase")]
pub(crate) enum ConfigKey { pub(crate) enum ConfigKey {
@ -76,19 +97,24 @@ impl Collection {
self.get_config_optional(key).unwrap_or_default() 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 where
K: Into<&'a str>, K: Into<&'a str>,
{ {
self.storage let entry = ConfigEntry::boxed(
.set_config_value(key.into(), val, self.usn()?, TimestampSecs::now()) 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 where
K: Into<&'a str>, 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 '_'. /// Remove all keys starting with provided prefix, which must end with '_'.
@ -107,7 +133,7 @@ impl Collection {
self.get_config_optional(ConfigKey::CreationOffset) self.get_config_optional(ConfigKey::CreationOffset)
} }
pub(crate) fn set_creation_utc_offset(&self, mins: Option<i32>) -> Result<()> { pub(crate) fn set_creation_utc_offset(&mut self, mins: Option<i32>) -> Result<()> {
if let Some(mins) = mins { if let Some(mins) = mins {
self.set_config(ConfigKey::CreationOffset, &mins) self.set_config(ConfigKey::CreationOffset, &mins)
} else { } else {
@ -119,7 +145,7 @@ impl Collection {
self.get_config_optional(ConfigKey::LocalOffset) 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) self.set_config(ConfigKey::LocalOffset, &mins)
} }
@ -128,7 +154,7 @@ impl Collection {
.map(|r| r.min(23)) .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) self.set_config(ConfigKey::Rollover, &hour)
} }
@ -136,7 +162,7 @@ impl Collection {
self.get_config_default(ConfigKey::NextNewCardPosition) self.get_config_default(ConfigKey::NextNewCardPosition)
} }
pub(crate) fn get_and_update_next_card_position(&self) -> Result<u32> { pub(crate) fn get_and_update_next_card_position(&mut self) -> Result<u32> {
let pos: u32 = self let pos: u32 = self
.get_config_optional(ConfigKey::NextNewCardPosition) .get_config_optional(ConfigKey::NextNewCardPosition)
.unwrap_or_default(); .unwrap_or_default();
@ -144,7 +170,7 @@ impl Collection {
Ok(pos) 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) self.set_config(ConfigKey::NextNewCardPosition, &pos)
} }
@ -154,7 +180,7 @@ impl Collection {
} }
/// Caution: this only updates the config setting. /// 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) self.set_config(ConfigKey::SchedulerVersion, &ver)
} }
@ -163,7 +189,7 @@ impl Collection {
.unwrap_or(1200) .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) 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)) self.set_config(ConfigKey::NewReviewMix, &(mix as u8))
} }
@ -184,7 +210,7 @@ impl Collection {
.unwrap_or(Weekday::Sunday) .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) self.set_config(ConfigKey::FirstDayOfWeek, &weekday)
} }
@ -193,7 +219,7 @@ impl Collection {
.unwrap_or_default() .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) self.set_config(ConfigKey::AnswerTimeLimitSecs, &secs)
} }
@ -202,7 +228,7 @@ impl Collection {
.unwrap_or_default() .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) self.set_config(ConfigKey::LastUnburiedDay, &day)
} }
} }
@ -274,7 +300,7 @@ mod test {
#[test] #[test]
fn get_set() { fn get_set() {
let col = open_test_collection(); let mut col = open_test_collection();
// missing key // missing key
assert_eq!(col.get_config_optional::<Vec<i64>, _>("test"), None); assert_eq!(col.get_config_optional::<Vec<i64>, _>("test"), None);

View file

@ -28,7 +28,7 @@ impl Collection {
self.get_config_optional(ConfigKey::CurrentNoteTypeID) 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) self.set_config(ConfigKey::CurrentNoteTypeID, &ntid)
} }
@ -41,7 +41,7 @@ impl Collection {
self.get_config_optional(key.as_str()) 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); let key = NoteTypeConfigKey::LastDeckAddedTo.for_notetype(id);
self.set_config(key.as_str(), &did) self.set_config(key.as_str(), &did)
} }

View file

@ -22,7 +22,7 @@ impl Collection {
.unwrap_or_else(|| default.to_string()) .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) self.set_config(key, &val)
} }
} }

111
rslib/src/config/undo.rs Normal file
View file

@ -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<ConfigEntry>),
Updated(Box<ConfigEntry>),
Removed(Box<ConfigEntry>),
}
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<ConfigEntry>) -> 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<ConfigEntry>) -> Result<()> {
self.storage.set_config_entry(&entry)?;
self.save_undo(UndoableConfigChange::Added(entry));
Ok(())
}
fn update_config_entry_undoable(
&mut self,
entry: Box<ConfigEntry>,
original: Box<ConfigEntry>,
) -> 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(())
}
}

View file

@ -409,7 +409,7 @@ impl Collection {
Ok(()) 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); let pos = self.storage.max_new_card_position().unwrap_or(0);
self.set_next_card_position(pos) self.set_next_card_position(pos)
} }

View file

@ -299,7 +299,12 @@ impl Collection {
// not sure if entry() can be used due to get_deck_config() returning a result // not sure if entry() can be used due to get_deck_config() returning a result
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
fn due_for_deck(&self, did: DeckID, dcid: DeckConfID, cache: &mut CardGenCache) -> Result<u32> { fn due_for_deck(
&mut self,
did: DeckID,
dcid: DeckConfID,
cache: &mut CardGenCache,
) -> Result<u32> {
if !cache.deck_configs.contains_key(&did) { if !cache.deck_configs.contains_key(&did) {
let conf = self.get_deck_config(dcid, true)?.unwrap(); let conf = self.get_deck_config(dcid, true)?.unwrap();
cache.deck_configs.insert(did, conf); cache.deck_configs.insert(did, conf);

View file

@ -3,8 +3,13 @@
use super::NoteTypeKind; use super::NoteTypeKind;
use crate::{ use crate::{
config::ConfigKey, err::Result, i18n::I18n, i18n::TR, notetype::NoteType, config::{ConfigEntry, ConfigKey},
storage::SqliteStorage, timestamp::TimestampSecs, err::Result,
i18n::I18n,
i18n::TR,
notetype::NoteType,
storage::SqliteStorage,
timestamp::TimestampSecs,
}; };
use crate::backend_proto::stock_note_type::Kind; 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() { for (idx, mut nt) in all_stock_notetypes(i18n).into_iter().enumerate() {
self.add_new_notetype(&mut nt)?; self.add_new_notetype(&mut nt)?;
if idx == Kind::Basic as usize { if idx == Kind::Basic as usize {
self.set_config_value( self.set_config_entry(&ConfigEntry::boxed(
ConfigKey::CurrentNoteTypeID.into(), ConfigKey::CurrentNoteTypeID.into(),
&nt.id, serde_json::to_vec(&nt.id)?,
self.usn(false)?, self.usn(false)?,
TimestampSecs::now(), TimestampSecs::now(),
)?; ))?;
} }
} }
Ok(()) Ok(())

View file

@ -193,7 +193,7 @@ impl Collection {
} }
/// Describe the next intervals, to display on the answer buttons. /// Describe the next intervals, to display on the answer buttons.
pub fn describe_next_states(&self, choices: NextCardStates) -> Result<Vec<String>> { pub fn describe_next_states(&mut self, choices: NextCardStates) -> Result<Vec<String>> {
let collapse_time = self.learn_ahead_secs(); let collapse_time = self.learn_ahead_secs();
let now = TimestampSecs::now(); let now = TimestampSecs::now();
let timing = self.timing_for_timestamp(now)?; let timing = self.timing_for_timestamp(now)?;

View file

@ -23,7 +23,7 @@ use timing::{
}; };
impl Collection { impl Collection {
pub fn timing_today(&self) -> Result<SchedTimingToday> { pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
self.timing_for_timestamp(TimestampSecs::now()) 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) Ok(((self.timing_today()?.days_elapsed as i32) + delta).max(0) as u32)
} }
pub(crate) fn timing_for_timestamp(&self, now: TimestampSecs) -> Result<SchedTimingToday> { pub(crate) fn timing_for_timestamp(&mut self, now: TimestampSecs) -> Result<SchedTimingToday> {
let current_utc_offset = self.local_utc_offset_for_user()?; let current_utc_offset = self.local_utc_offset_for_user()?;
let rollover_hour = match self.scheduler_version() { let rollover_hour = match self.scheduler_version() {
@ -63,7 +63,7 @@ impl Collection {
/// ensuring the config reflects the current value. /// ensuring the config reflects the current value.
/// In the server case, return the value set in the config, and /// In the server case, return the value set in the config, and
/// fall back on UTC if it's missing/invalid. /// fall back on UTC if it's missing/invalid.
pub(crate) fn local_utc_offset_for_user(&self) -> Result<FixedOffset> { pub(crate) fn local_utc_offset_for_user(&mut self) -> Result<FixedOffset> {
let config_tz = self let config_tz = self
.get_configured_utc_offset() .get_configured_utc_offset()
.and_then(|v| FixedOffset::west_opt(v * 60)) .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() { match self.scheduler_version() {
SchedulerVersion::V1 => { SchedulerVersion::V1 => {
self.storage self.storage

View file

@ -126,7 +126,7 @@ impl Collection {
}) })
} }
fn card_stats_to_string(&self, cs: CardStats) -> Result<String> { fn card_stats_to_string(&mut self, cs: CardStats) -> Result<String> {
let offset = self.local_utc_offset_for_user()?; let offset = self.local_utc_offset_for_user()?;
let i18n = &self.i18n; let i18n = &self.i18n;

View file

@ -20,7 +20,7 @@ impl Collection {
self.graph_data(all, days) self.graph_data(all, days)
} }
fn graph_data(&self, all: bool, days: u32) -> Result<pb::GraphsOut> { fn graph_data(&mut self, all: bool, days: u32) -> Result<pb::GraphsOut> {
let timing = self.timing_today()?; let timing = self.timing_today()?;
let revlog_start = TimestampSecs(if days > 0 { let revlog_start = TimestampSecs(if days > 0 {
timing.next_day_at - (((days as i64) + 1) * 86_400) 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 { self.set_first_day_of_week(match prefs.calendar_first_day_of_week {
1 => Weekday::Monday, 1 => Weekday::Monday,
5 => Weekday::Friday, 5 => Weekday::Friday,

View file

@ -18,10 +18,9 @@ pub fn studied_today(cards: u32, secs: f32, i18n: &I18n) -> String {
} }
impl Collection { impl Collection {
pub fn studied_today(&self) -> Result<String> { pub fn studied_today(&mut self) -> Result<String> {
let today = self let timing = self.timing_today()?;
.storage let today = self.storage.studied_today(timing.next_day_at)?;
.studied_today(self.timing_today()?.next_day_at)?;
Ok(studied_today(today.cards, today.seconds as f32, &self.i18n)) Ok(studied_today(today.cards, today.seconds as f32, &self.i18n))
} }
} }

View file

@ -0,0 +1,5 @@
SELECT val,
usn,
mtime_secs
FROM config
WHERE KEY = ?

View file

@ -2,24 +2,17 @@
// 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
use super::SqliteStorage; 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 rusqlite::{params, NO_PARAMS};
use serde::{de::DeserializeOwned, Serialize}; use serde::de::DeserializeOwned;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
impl SqliteStorage { impl SqliteStorage {
pub(crate) fn set_config_value<T: Serialize>( pub(crate) fn set_config_entry(&self, entry: &ConfigEntry) -> Result<()> {
&self,
key: &str,
val: &T,
usn: Usn,
mtime: TimestampSecs,
) -> Result<()> {
let json = serde_json::to_vec(val)?;
self.db self.db
.prepare_cached(include_str!("add.sql"))? .prepare_cached(include_str!("add.sql"))?
.execute(params![key, usn, mtime, &json])?; .execute(params![&entry.key, entry.usn, entry.mtime, &entry.value])?;
Ok(()) Ok(())
} }
@ -41,6 +34,22 @@ impl SqliteStorage {
.transpose() .transpose()
} }
/// Return the raw bytes and other metadata, for undoing.
pub(crate) fn get_config_entry(&self, key: &str) -> Result<Option<Box<ConfigEntry>>> {
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 '_'. /// Prefix is expected to end with '_'.
pub(crate) fn get_config_prefix(&self, prefix: &str) -> Result<Vec<(String, Vec<u8>)>> { pub(crate) fn get_config_prefix(&self, prefix: &str) -> Result<Vec<(String, Vec<u8>)>> {
let mut end = prefix.to_string(); let mut end = prefix.to_string();
@ -70,7 +79,12 @@ impl SqliteStorage {
) -> Result<()> { ) -> Result<()> {
self.db.execute("delete from config", NO_PARAMS)?; self.db.execute("delete from config", NO_PARAMS)?;
for (key, val) in conf.iter() { 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(()) Ok(())
} }

View file

@ -328,8 +328,8 @@ where
SyncActionRequired::NormalSyncRequired => { SyncActionRequired::NormalSyncRequired => {
self.col.discard_undo_and_study_queues(); self.col.discard_undo_and_study_queues();
self.col.storage.begin_trx()?; self.col.storage.begin_trx()?;
self.col let timing = self.col.timing_today()?;
.unbury_if_day_rolled_over(self.col.timing_today()?)?; self.col.unbury_if_day_rolled_over(timing)?;
match self.normal_sync_inner(state).await { match self.normal_sync_inner(state).await {
Ok(success) => { Ok(success) => {
self.col.storage.commit_trx()?; self.col.storage.commit_trx()?;

View file

@ -2,9 +2,10 @@
// 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
use crate::{ use crate::{
card::undo::UndoableCardChange, decks::undo::UndoableDeckChange, card::undo::UndoableCardChange, config::undo::UndoableConfigChange,
notes::undo::UndoableNoteChange, prelude::*, revlog::undo::UndoableRevlogChange, decks::undo::UndoableDeckChange, notes::undo::UndoableNoteChange, prelude::*,
scheduler::queue::undo::UndoableQueueChange, tags::undo::UndoableTagChange, revlog::undo::UndoableRevlogChange, scheduler::queue::undo::UndoableQueueChange,
tags::undo::UndoableTagChange,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -15,6 +16,7 @@ pub(crate) enum UndoableChange {
Tag(UndoableTagChange), Tag(UndoableTagChange),
Revlog(UndoableRevlogChange), Revlog(UndoableRevlogChange),
Queue(UndoableQueueChange), Queue(UndoableQueueChange),
Config(UndoableConfigChange),
} }
impl UndoableChange { impl UndoableChange {
@ -26,6 +28,7 @@ impl UndoableChange {
UndoableChange::Tag(c) => col.undo_tag_change(c), UndoableChange::Tag(c) => col.undo_tag_change(c),
UndoableChange::Revlog(c) => col.undo_revlog_change(c), UndoableChange::Revlog(c) => col.undo_revlog_change(c),
UndoableChange::Queue(c) => col.undo_queue_change(c), UndoableChange::Queue(c) => col.undo_queue_change(c),
UndoableChange::Config(c) => col.undo_config_change(c),
} }
} }
} }
@ -65,3 +68,9 @@ impl From<UndoableQueueChange> for UndoableChange {
UndoableChange::Queue(c) UndoableChange::Queue(c)
} }
} }
impl From<UndoableConfigChange> for UndoableChange {
fn from(c: UndoableConfigChange) -> Self {
UndoableChange::Config(c)
}
}