undo support for config entries
This commit is contained in:
parent
ce243c2cae
commit
96940f0527
@ -44,7 +44,7 @@ pub fn open_collection<P: Into<PathBuf>>(
|
||||
#[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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<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)]
|
||||
#[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<i32>) -> Result<()> {
|
||||
pub(crate) fn set_creation_utc_offset(&mut self, mins: Option<i32>) -> 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<u32> {
|
||||
pub(crate) fn get_and_update_next_card_position(&mut self) -> Result<u32> {
|
||||
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::<Vec<i64>, _>("test"), None);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
111
rslib/src/config/undo.rs
Normal file
111
rslib/src/config/undo.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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<u32> {
|
||||
fn due_for_deck(
|
||||
&mut self,
|
||||
did: DeckID,
|
||||
dcid: DeckConfID,
|
||||
cache: &mut CardGenCache,
|
||||
) -> Result<u32> {
|
||||
if !cache.deck_configs.contains_key(&did) {
|
||||
let conf = self.get_deck_config(dcid, true)?.unwrap();
|
||||
cache.deck_configs.insert(did, conf);
|
||||
|
@ -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(())
|
||||
|
@ -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<Vec<String>> {
|
||||
pub fn describe_next_states(&mut self, choices: NextCardStates) -> Result<Vec<String>> {
|
||||
let collapse_time = self.learn_ahead_secs();
|
||||
let now = TimestampSecs::now();
|
||||
let timing = self.timing_for_timestamp(now)?;
|
||||
|
@ -23,7 +23,7 @@ use timing::{
|
||||
};
|
||||
|
||||
impl Collection {
|
||||
pub fn timing_today(&self) -> Result<SchedTimingToday> {
|
||||
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
|
||||
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<SchedTimingToday> {
|
||||
pub(crate) fn timing_for_timestamp(&mut self, now: TimestampSecs) -> Result<SchedTimingToday> {
|
||||
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<FixedOffset> {
|
||||
pub(crate) fn local_utc_offset_for_user(&mut self) -> Result<FixedOffset> {
|
||||
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
|
||||
|
@ -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 i18n = &self.i18n;
|
||||
|
||||
|
@ -20,7 +20,7 @@ impl Collection {
|
||||
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 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,
|
||||
|
@ -18,10 +18,9 @@ pub fn studied_today(cards: u32, secs: f32, i18n: &I18n) -> String {
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn studied_today(&self) -> Result<String> {
|
||||
let today = self
|
||||
.storage
|
||||
.studied_today(self.timing_today()?.next_day_at)?;
|
||||
pub fn studied_today(&mut self) -> Result<String> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
5
rslib/src/storage/config/get_entry.sql
Normal file
5
rslib/src/storage/config/get_entry.sql
Normal file
@ -0,0 +1,5 @@
|
||||
SELECT val,
|
||||
usn,
|
||||
mtime_secs
|
||||
FROM config
|
||||
WHERE KEY = ?
|
@ -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<T: Serialize>(
|
||||
&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<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 '_'.
|
||||
pub(crate) fn get_config_prefix(&self, prefix: &str) -> Result<Vec<(String, Vec<u8>)>> {
|
||||
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(())
|
||||
}
|
||||
|
@ -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()?;
|
||||
|
@ -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<UndoableQueueChange> for UndoableChange {
|
||||
UndoableChange::Queue(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableConfigChange> for UndoableChange {
|
||||
fn from(c: UndoableConfigChange) -> Self {
|
||||
UndoableChange::Config(c)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user