mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
undo support for config entries
This commit is contained in:
parent
ce243c2cae
commit
96940f0527
19 changed files with 237 additions and 59 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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(())
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
// 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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue