From 9317cee9baacd5e118edc6557d16b8448b09ebc5 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 11 May 2020 18:35:15 +1000 Subject: [PATCH] handle scheduling preferences in the backend --- proto/backend.proto | 28 ++++++++++++ pylib/anki/collection.py | 16 ++++--- pylib/anki/rsbackend.py | 8 ++++ pylib/anki/schedv2.py | 20 --------- qt/aqt/preferences.py | 89 +++++++++++++------------------------ qt/mypy.ini | 2 + rslib/src/backend/mod.rs | 15 ++++++- rslib/src/config.rs | 89 +++++++++++++++++++++++++++++++++++-- rslib/src/lib.rs | 1 + rslib/src/preferences.rs | 85 +++++++++++++++++++++++++++++++++++ rslib/src/sched/cutoff.rs | 13 ++++++ rslib/src/sched/mod.rs | 22 +++++++-- rslib/src/storage/sqlite.rs | 7 +++ 13 files changed, 305 insertions(+), 90 deletions(-) create mode 100644 rslib/src/preferences.rs diff --git a/proto/backend.proto b/proto/backend.proto index c383d9d72..beef8824c 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -95,6 +95,8 @@ message BackendInput { AddNoteTagsIn add_note_tags = 81; UpdateNoteTagsIn update_note_tags = 82; int32 set_local_minutes_west = 83; + Empty get_preferences = 84; + Preferences set_preferences = 85; } } @@ -169,6 +171,8 @@ message BackendOutput { uint32 add_note_tags = 81; uint32 update_note_tags = 82; Empty set_local_minutes_west = 83; + Preferences get_preferences = 84; + Empty set_preferences = 85; BackendError error = 2047; } @@ -747,3 +751,27 @@ message UpdateNoteTagsIn { message CheckDatabaseOut { repeated string problems = 1; } + +message CollectionSchedulingSettings { + enum NewReviewMix { + DISTRIBUTE = 0; + REVIEWS_FIRST = 1; + NEW_FIRST = 2; + } + + uint32 scheduler_version = 1; + uint32 rollover = 2; + uint32 learn_ahead_secs = 3; + NewReviewMix new_review_mix = 4; + bool show_remaining_due_counts = 5; + bool show_intervals_on_buttons = 6; + uint32 time_limit_secs = 7; + + // v2 only + bool new_timezone = 8; + bool day_learn_first = 9; +} + +message Preferences { + CollectionSchedulingSettings sched = 1; +} diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 6a9b96adf..2a640ddca 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -38,7 +38,6 @@ from anki.utils import devMode, ids2str, intTime, joinFields class _Collection: db: Optional[DBProxy] sched: Union[V1Scheduler, V2Scheduler] - crt: int mod: int scm: int _usn: int @@ -132,11 +131,19 @@ class _Collection: ########################################################################## def load(self) -> None: - (self.crt, self.mod, self.scm, self._usn, self.ls,) = self.db.first( + (self.mod, self.scm, self._usn, self.ls,) = self.db.first( """ -select crt, mod, scm, usn, ls from col""" +select mod, scm, usn, ls from col""" ) + def _get_crt(self) -> int: + return self.db.scalar("select crt from col") + + def _set_crt(self, val: int) -> None: + self.db.execute("update col set crt=?", val) + + crt = property(_get_crt, _set_crt) + def setMod(self) -> None: """Mark DB modified. @@ -149,8 +156,7 @@ is only necessary if you modify properties of this object.""" self.mod = intTime(1000) if mod is None else mod self.db.execute( """update col set -crt=?, mod=?, scm=?, usn=?, ls=?""", - self.crt, +mod=?, scm=?, usn=?, ls=?""", self.mod, self.scm, self._usn, diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 7c5efeb9d..c75e8897c 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -782,6 +782,14 @@ class RustBackend: def set_local_minutes_west(self, mins: int) -> None: self._run_command(pb.BackendInput(set_local_minutes_west=mins)) + def get_preferences(self) -> pb.Preferences: + return self._run_command( + pb.BackendInput(get_preferences=pb.Empty()) + ).get_preferences + + def set_preferences(self, prefs: pb.Preferences) -> None: + self._run_command(pb.BackendInput(set_preferences=prefs)) + def translate_string_in( key: TR, **kwargs: Union[str, int, float] diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index edba3ab37..6c55ac706 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -1377,26 +1377,6 @@ where id = ? def _timing_today(self) -> SchedTimingToday: return self.col.backend.sched_timing_today() - # New timezone handling - GUI helpers - ########################################################################## - - def new_timezone_enabled(self) -> bool: - return self.col.conf.get("creationOffset") is not None - - def set_creation_offset(self): - """Save the UTC west offset at the time of creation into the DB. - - Once stored, this activates the new timezone handling code. - """ - mins_west = self.col.backend.local_minutes_west(self.col.crt) - self.col.conf["creationOffset"] = mins_west - self.col.setMod() - - def clear_creation_offset(self): - if "creationOffset" in self.col.conf: - del self.col.conf["creationOffset"] - self.col.setMod() - # Deck finished state ########################################################################## diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 5f6f705a1..1688c341e 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -2,9 +2,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import datetime -import time - import anki.lang import aqt from anki.lang import _ @@ -24,6 +21,7 @@ class Preferences(QDialog): self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False) qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("profileprefs")) self.silentlyClose = True + self.prefs = self.mw.col.backend.get_preferences() self.setupLang() self.setupCollection() self.setupNetwork() @@ -81,25 +79,31 @@ class Preferences(QDialog): f = self.form qc = self.mw.col.conf - self._setupDayCutoff() + if isMac: f.hwAccel.setVisible(False) else: f.hwAccel.setChecked(self.mw.pm.glMode() != "software") - f.lrnCutoff.setValue(qc["collapseTime"] / 60.0) - f.timeLimit.setValue(qc["timeLim"] / 60.0) - f.showEstimates.setChecked(qc["estTimes"]) - f.showProgress.setChecked(qc["dueCounts"]) + f.newSpread.addItems(list(c.newCardSchedulingLabels().values())) - f.newSpread.setCurrentIndex(qc["newSpread"]) + f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True))) - f.dayLearnFirst.setChecked(qc.get("dayLearnFirst", False)) - if self.mw.col.schedVer() != 2: + + s = self.prefs.sched + f.lrnCutoff.setValue(s.learn_ahead_secs / 60.0) + f.timeLimit.setValue(s.time_limit_secs / 60.0) + f.showEstimates.setChecked(s.show_intervals_on_buttons) + f.showProgress.setChecked(s.show_remaining_due_counts) + f.newSpread.setCurrentIndex(s.new_review_mix) + f.dayLearnFirst.setChecked(s.day_learn_first) + f.dayOffset.setValue(s.rollover) + + if s.scheduler_version < 2: f.dayLearnFirst.setVisible(False) f.new_timezone.setVisible(False) else: f.newSched.setChecked(True) - f.new_timezone.setChecked(self.mw.col.sched.new_timezone_enabled()) + f.new_timezone.setChecked(s.new_timezone) def updateCollection(self): f = self.form @@ -116,22 +120,22 @@ class Preferences(QDialog): showInfo(_("Changes will take effect when you restart Anki.")) qc = d.conf - qc["dueCounts"] = f.showProgress.isChecked() - qc["estTimes"] = f.showEstimates.isChecked() - qc["newSpread"] = f.newSpread.currentIndex() - qc["timeLim"] = f.timeLimit.value() * 60 - qc["collapseTime"] = f.lrnCutoff.value() * 60 qc["addToCur"] = not f.useCurrent.currentIndex() - qc["dayLearnFirst"] = f.dayLearnFirst.isChecked() - self._updateDayCutoff() - if self.mw.col.schedVer() != 1: - was_enabled = self.mw.col.sched.new_timezone_enabled() - is_enabled = f.new_timezone.isChecked() - if was_enabled != is_enabled: - if is_enabled: - self.mw.col.sched.set_creation_offset() - else: - self.mw.col.sched.clear_creation_offset() + + s = self.prefs.sched + s.show_remaining_due_counts = f.showProgress.isChecked() + s.show_intervals_on_buttons = f.showEstimates.isChecked() + s.new_review_mix = f.newSpread.currentIndex() + s.time_limit_secs = f.timeLimit.value() * 60 + s.learn_ahead_secs = f.lrnCutoff.value() * 60 + s.day_learn_first = f.dayLearnFirst.isChecked() + s.rollover = f.dayOffset.value() + s.new_timezone = f.new_timezone.isChecked() + + # if moving this, make sure scheduler change is moved to Rust or + # happens afterwards + self.mw.col.backend.set_preferences(self.prefs) + self._updateSchedVer(f.newSched.isChecked()) d.setMod() @@ -157,37 +161,6 @@ class Preferences(QDialog): else: self.mw.col.changeSchedulerVer(1) - # Day cutoff - ###################################################################### - - def _setupDayCutoff(self): - if self.mw.col.schedVer() == 2: - self._setupDayCutoffV2() - else: - self._setupDayCutoffV1() - - def _setupDayCutoffV1(self): - self.startDate = datetime.datetime.fromtimestamp(self.mw.col.crt) - self.form.dayOffset.setValue(self.startDate.hour) - - def _setupDayCutoffV2(self): - self.form.dayOffset.setValue(self.mw.col.conf.get("rollover", 4)) - - def _updateDayCutoff(self): - if self.mw.col.schedVer() == 2: - self._updateDayCutoffV2() - else: - self._updateDayCutoffV1() - - def _updateDayCutoffV1(self): - hrs = self.form.dayOffset.value() - old = self.startDate - date = datetime.datetime(old.year, old.month, old.day, hrs) - self.mw.col.crt = int(time.mktime(date.timetuple())) - - def _updateDayCutoffV2(self): - self.mw.col.conf["rollover"] = self.form.dayOffset.value() - # Network ###################################################################### diff --git a/qt/mypy.ini b/qt/mypy.ini index 0089378e2..5f88a2a12 100644 --- a/qt/mypy.ini +++ b/qt/mypy.ini @@ -60,3 +60,5 @@ check_untyped_defs=true check_untyped_defs=true [mypy-aqt.clayout] check_untyped_defs=true +[mypy-aqt.preferences] +check_untyped_defs=true diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index bacd8ff9c..dd2051d6a 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -368,6 +368,11 @@ impl Backend { self.set_local_mins_west(mins)?; pb::Empty {} }), + Value::GetPreferences(_) => OValue::GetPreferences(self.get_preferences()?), + Value::SetPreferences(prefs) => OValue::SetPreferences({ + self.set_preferences(prefs)?; + pb::Empty {} + }), }) } @@ -820,7 +825,7 @@ impl Backend { let val: JsonValue = serde_json::from_slice(&val)?; col.set_config(input.key.as_str(), &val) } - pb::set_config_json::Op::Remove(_) => col.remove_config(&input.key), + pb::set_config_json::Op::Remove(_) => col.remove_config(input.key.as_str()), } } else { Err(AnkiError::invalid_input("no op received")) @@ -1115,6 +1120,14 @@ impl Backend { fn set_local_mins_west(&self, mins: i32) -> Result<()> { self.with_col(|col| col.transact(None, |col| col.set_local_mins_west(mins))) } + + fn get_preferences(&self) -> Result { + self.with_col(|col| col.get_preferences()) + } + + fn set_preferences(&self, prefs: pb::Preferences) -> Result<()> { + self.with_col(|col| col.transact(None, |col| col.set_preferences(prefs))) + } } fn to_nids(ids: Vec) -> Vec { diff --git a/rslib/src/config.rs b/rslib/src/config.rs index 992612d21..ccbe6d97e 100644 --- a/rslib/src/config.rs +++ b/rslib/src/config.rs @@ -44,6 +44,11 @@ pub(crate) enum ConfigKey { SchedulerVersion, LearnAheadSecs, NormalizeNoteText, + ShowRemainingDueCountsInStudy, + ShowIntervalsAboveAnswerButtons, + NewReviewMix, + AnswerTimeLimitSecs, + ShowDayLearningCardsFirst, } #[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)] #[repr(u8)] @@ -66,6 +71,11 @@ impl From for &'static str { ConfigKey::SchedulerVersion => "schedVer", ConfigKey::LearnAheadSecs => "collapseTime", ConfigKey::NormalizeNoteText => "normalize_note_text", + ConfigKey::ShowRemainingDueCountsInStudy => "dueCounts", + ConfigKey::ShowIntervalsAboveAnswerButtons => "estTimes", + ConfigKey::NewReviewMix => "newSpread", + ConfigKey::AnswerTimeLimitSecs => "timeLim", + ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst", } } } @@ -108,8 +118,11 @@ impl Collection { .set_config_value(key.into(), val, self.usn()?, TimestampSecs::now()) } - pub(crate) fn remove_config(&self, key: &str) -> Result<()> { - self.storage.remove_config(key) + pub(crate) fn remove_config<'a, K>(&self, key: K) -> Result<()> + where + K: Into<&'a str>, + { + self.storage.remove_config(key.into()) } pub(crate) fn get_browser_sort_kind(&self) -> SortKind { @@ -130,6 +143,14 @@ impl Collection { self.get_config_optional(ConfigKey::CreationOffset) } + pub(crate) fn set_creation_mins_west(&self, mins: Option) -> Result<()> { + if let Some(mins) = mins { + self.set_config(ConfigKey::CreationOffset, &mins) + } else { + self.remove_config(ConfigKey::CreationOffset) + } + } + pub(crate) fn get_local_mins_west(&self) -> Option { self.get_config_optional(ConfigKey::LocalOffset) } @@ -138,11 +159,15 @@ impl Collection { self.set_config(ConfigKey::LocalOffset, &mins) } - pub(crate) fn get_rollover(&self) -> Option { + pub(crate) fn get_v2_rollover(&self) -> Option { self.get_config_optional::(ConfigKey::Rollover) .map(|r| r.min(23)) } + pub(crate) fn set_v2_rollover(&self, hour: u32) -> Result<()> { + self.set_config(ConfigKey::Rollover, &hour) + } + #[allow(dead_code)] pub(crate) fn get_current_notetype_id(&self) -> Option { self.get_config_optional(ConfigKey::CurrentNoteTypeID) @@ -175,11 +200,63 @@ impl Collection { .unwrap_or(1200) } + pub(crate) fn set_learn_ahead_secs(&self, secs: u32) -> Result<()> { + self.set_config(ConfigKey::LearnAheadSecs, &secs) + } + /// This is a stop-gap solution until we can decouple searching from canonical storage. pub(crate) fn normalize_note_text(&self) -> bool { self.get_config_optional(ConfigKey::NormalizeNoteText) .unwrap_or(true) } + + pub(crate) fn get_new_review_mix(&self) -> NewReviewMix { + match self.get_config_default::(ConfigKey::NewReviewMix) { + 1 => NewReviewMix::ReviewsFirst, + 2 => NewReviewMix::NewFirst, + _ => NewReviewMix::Mix, + } + } + + pub(crate) fn set_new_review_mix(&self, mix: NewReviewMix) -> Result<()> { + self.set_config(ConfigKey::NewReviewMix, &(mix as u8)) + } + + pub(crate) fn get_show_due_counts(&self) -> bool { + self.get_config_optional(ConfigKey::ShowRemainingDueCountsInStudy) + .unwrap_or(true) + } + + pub(crate) fn set_show_due_counts(&self, on: bool) -> Result<()> { + self.set_config(ConfigKey::ShowRemainingDueCountsInStudy, &on) + } + + pub(crate) fn get_show_intervals_above_buttons(&self) -> bool { + self.get_config_optional(ConfigKey::ShowIntervalsAboveAnswerButtons) + .unwrap_or(true) + } + + pub(crate) fn set_show_intervals_above_buttons(&self, on: bool) -> Result<()> { + self.set_config(ConfigKey::ShowIntervalsAboveAnswerButtons, &on) + } + + pub(crate) fn get_answer_time_limit_secs(&self) -> u32 { + self.get_config_optional(ConfigKey::AnswerTimeLimitSecs) + .unwrap_or_default() + } + + pub(crate) fn set_answer_time_limit_secs(&self, secs: u32) -> Result<()> { + self.set_config(ConfigKey::AnswerTimeLimitSecs, &secs) + } + + pub(crate) fn get_day_learn_first(&self) -> bool { + self.get_config_optional(ConfigKey::ShowDayLearningCardsFirst) + .unwrap_or_default() + } + + pub(crate) fn set_day_learn_first(&self, on: bool) -> Result<()> { + self.set_config(ConfigKey::ShowDayLearningCardsFirst, &on) + } } #[derive(Deserialize, PartialEq, Debug, Clone, Copy)] @@ -212,6 +289,12 @@ impl Default for SortKind { } } +pub(crate) enum NewReviewMix { + Mix = 0, + ReviewsFirst = 1, + NewFirst = 2, +} + #[cfg(test)] mod test { use super::SortKind; diff --git a/rslib/src/lib.rs b/rslib/src/lib.rs index caa7ee274..e5d00afe5 100644 --- a/rslib/src/lib.rs +++ b/rslib/src/lib.rs @@ -25,6 +25,7 @@ pub mod log; pub mod media; pub mod notes; pub mod notetype; +mod preferences; pub mod sched; pub mod search; pub mod serde; diff --git a/rslib/src/preferences.rs b/rslib/src/preferences.rs new file mode 100644 index 000000000..93231c41f --- /dev/null +++ b/rslib/src/preferences.rs @@ -0,0 +1,85 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::{ + backend_proto::{ + collection_scheduling_settings::NewReviewMix as NewRevMixPB, CollectionSchedulingSettings, + Preferences, + }, + collection::Collection, + err::Result, + sched::cutoff::local_minutes_west_for_stamp, +}; + +impl Collection { + pub fn get_preferences(&self) -> Result { + Ok(Preferences { + sched: Some(self.get_collection_scheduling_settings()?), + }) + } + + pub fn set_preferences(&self, prefs: Preferences) -> Result<()> { + if let Some(sched) = prefs.sched { + self.set_collection_scheduling_settings(sched)?; + } + + Ok(()) + } + + pub fn get_collection_scheduling_settings(&self) -> Result { + Ok(CollectionSchedulingSettings { + scheduler_version: match self.sched_ver() { + crate::config::SchedulerVersion::V1 => 1, + crate::config::SchedulerVersion::V2 => 2, + }, + rollover: self.rollover_for_current_scheduler()? as u32, + learn_ahead_secs: self.learn_ahead_secs(), + new_review_mix: match self.get_new_review_mix() { + crate::config::NewReviewMix::Mix => NewRevMixPB::Distribute, + crate::config::NewReviewMix::ReviewsFirst => NewRevMixPB::ReviewsFirst, + crate::config::NewReviewMix::NewFirst => NewRevMixPB::NewFirst, + } as i32, + show_remaining_due_counts: self.get_show_due_counts(), + show_intervals_on_buttons: self.get_show_intervals_above_buttons(), + time_limit_secs: self.get_answer_time_limit_secs(), + new_timezone: self.get_creation_mins_west().is_some(), + day_learn_first: self.get_day_learn_first(), + }) + } + + pub(crate) fn set_collection_scheduling_settings( + &self, + settings: CollectionSchedulingSettings, + ) -> Result<()> { + let s = settings; + + self.set_day_learn_first(s.day_learn_first)?; + self.set_answer_time_limit_secs(s.time_limit_secs)?; + self.set_show_due_counts(s.show_remaining_due_counts)?; + self.set_show_intervals_above_buttons(s.show_intervals_on_buttons)?; + self.set_learn_ahead_secs(s.learn_ahead_secs)?; + + self.set_new_review_mix(match s.new_review_mix() { + NewRevMixPB::Distribute => crate::config::NewReviewMix::Mix, + NewRevMixPB::NewFirst => crate::config::NewReviewMix::NewFirst, + NewRevMixPB::ReviewsFirst => crate::config::NewReviewMix::ReviewsFirst, + })?; + + let created = self.storage.creation_stamp()?; + + if self.rollover_for_current_scheduler()? != s.rollover as u8 { + self.set_rollover_for_current_scheduler(s.rollover as u8)?; + } + + if s.new_timezone { + if self.get_creation_mins_west().is_none() { + self.set_creation_mins_west(Some(local_minutes_west_for_stamp(created.0)))?; + } + } else { + self.set_creation_mins_west(None)?; + } + + // fixme: currently scheduler change unhandled + Ok(()) + } +} diff --git a/rslib/src/sched/cutoff.rs b/rslib/src/sched/cutoff.rs index 233a78b45..26d941384 100644 --- a/rslib/src/sched/cutoff.rs +++ b/rslib/src/sched/cutoff.rs @@ -105,6 +105,14 @@ fn v1_creation_date_inner(now: TimestampSecs, mins_west: i32) -> i64 { } } +pub(crate) fn v1_creation_date_adjusted_to_hour(crt: i64, hour: u8) -> i64 { + Local + .timestamp(crt, 0) + .date() + .and_hms(hour as u32, 0, 0) + .timestamp() +} + fn sched_timing_today_v1(crt: i64, now: i64) -> SchedTimingToday { let days_elapsed = (now - crt) / 86_400; let next_day_at = crt + (days_elapsed + 1) * 86_400; @@ -376,5 +384,10 @@ mod test { v1_creation_date_inner(now, AEST_MINS_WEST), offset.ymd(2020, 05, 9).and_hms(4, 0, 0).timestamp() ); + + let crt = v1_creation_date_inner(now, AEST_MINS_WEST); + assert_eq!(crt, v1_creation_date_adjusted_to_hour(crt, 4)); + assert_eq!(crt + 3600, v1_creation_date_adjusted_to_hour(crt, 5)); + assert_eq!(crt - 3600 * 4, v1_creation_date_adjusted_to_hour(crt, 0)); } } diff --git a/rslib/src/sched/mod.rs b/rslib/src/sched/mod.rs index d8571d19b..dff0a9508 100644 --- a/rslib/src/sched/mod.rs +++ b/rslib/src/sched/mod.rs @@ -8,7 +8,10 @@ use crate::{ pub mod cutoff; pub mod timespan; -use cutoff::{sched_timing_today, v1_rollover_from_creation_stamp, SchedTimingToday}; +use cutoff::{ + sched_timing_today, v1_creation_date_adjusted_to_hour, v1_rollover_from_creation_stamp, + SchedTimingToday, +}; impl Collection { pub fn timing_today(&mut self) -> Result { @@ -27,7 +30,7 @@ impl Collection { now, self.get_creation_mins_west(), local_offset, - self.get_rollover(), + self.get_v2_rollover(), )) } @@ -36,7 +39,20 @@ impl Collection { SchedulerVersion::V1 => Ok(v1_rollover_from_creation_stamp( self.storage.creation_stamp()?.0, )), - SchedulerVersion::V2 => Ok(self.get_rollover().unwrap_or(4)), + SchedulerVersion::V2 => Ok(self.get_v2_rollover().unwrap_or(4)), + } + } + + pub(crate) fn set_rollover_for_current_scheduler(&self, hour: u8) -> Result<()> { + match self.sched_ver() { + SchedulerVersion::V1 => { + self.storage + .set_creation_stamp(TimestampSecs(v1_creation_date_adjusted_to_hour( + self.storage.creation_stamp()?.0, + hour, + ))) + } + SchedulerVersion::V2 => self.set_v2_rollover(hour as u32), } } diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index b5bf5efcb..6f2870319 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -294,6 +294,13 @@ impl SqliteStorage { .map_err(Into::into) } + pub(crate) fn set_creation_stamp(&self, stamp: TimestampSecs) -> Result<()> { + self.db + .prepare("update col set crt = ?")? + .execute(&[stamp])?; + Ok(()) + } + pub(crate) fn set_schema_modified(&self) -> Result<()> { self.db .prepare_cached("update col set scm = ?")?