mirror of
https://github.com/ankitects/anki.git
synced 2025-12-20 10:22:57 -05:00
handle scheduling preferences in the backend
This commit is contained in:
parent
6ef8d976eb
commit
9317cee9ba
13 changed files with 305 additions and 90 deletions
|
|
@ -95,6 +95,8 @@ message BackendInput {
|
||||||
AddNoteTagsIn add_note_tags = 81;
|
AddNoteTagsIn add_note_tags = 81;
|
||||||
UpdateNoteTagsIn update_note_tags = 82;
|
UpdateNoteTagsIn update_note_tags = 82;
|
||||||
int32 set_local_minutes_west = 83;
|
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 add_note_tags = 81;
|
||||||
uint32 update_note_tags = 82;
|
uint32 update_note_tags = 82;
|
||||||
Empty set_local_minutes_west = 83;
|
Empty set_local_minutes_west = 83;
|
||||||
|
Preferences get_preferences = 84;
|
||||||
|
Empty set_preferences = 85;
|
||||||
|
|
||||||
BackendError error = 2047;
|
BackendError error = 2047;
|
||||||
}
|
}
|
||||||
|
|
@ -747,3 +751,27 @@ message UpdateNoteTagsIn {
|
||||||
message CheckDatabaseOut {
|
message CheckDatabaseOut {
|
||||||
repeated string problems = 1;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ from anki.utils import devMode, ids2str, intTime, joinFields
|
||||||
class _Collection:
|
class _Collection:
|
||||||
db: Optional[DBProxy]
|
db: Optional[DBProxy]
|
||||||
sched: Union[V1Scheduler, V2Scheduler]
|
sched: Union[V1Scheduler, V2Scheduler]
|
||||||
crt: int
|
|
||||||
mod: int
|
mod: int
|
||||||
scm: int
|
scm: int
|
||||||
_usn: int
|
_usn: int
|
||||||
|
|
@ -132,11 +131,19 @@ class _Collection:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def load(self) -> None:
|
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:
|
def setMod(self) -> None:
|
||||||
"""Mark DB modified.
|
"""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.mod = intTime(1000) if mod is None else mod
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"""update col set
|
"""update col set
|
||||||
crt=?, mod=?, scm=?, usn=?, ls=?""",
|
mod=?, scm=?, usn=?, ls=?""",
|
||||||
self.crt,
|
|
||||||
self.mod,
|
self.mod,
|
||||||
self.scm,
|
self.scm,
|
||||||
self._usn,
|
self._usn,
|
||||||
|
|
|
||||||
|
|
@ -782,6 +782,14 @@ class RustBackend:
|
||||||
def set_local_minutes_west(self, mins: int) -> None:
|
def set_local_minutes_west(self, mins: int) -> None:
|
||||||
self._run_command(pb.BackendInput(set_local_minutes_west=mins))
|
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(
|
def translate_string_in(
|
||||||
key: TR, **kwargs: Union[str, int, float]
|
key: TR, **kwargs: Union[str, int, float]
|
||||||
|
|
|
||||||
|
|
@ -1377,26 +1377,6 @@ where id = ?
|
||||||
def _timing_today(self) -> SchedTimingToday:
|
def _timing_today(self) -> SchedTimingToday:
|
||||||
return self.col.backend.sched_timing_today()
|
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
|
# Deck finished state
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# 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
|
||||||
|
|
||||||
import datetime
|
|
||||||
import time
|
|
||||||
|
|
||||||
import anki.lang
|
import anki.lang
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
@ -24,6 +21,7 @@ class Preferences(QDialog):
|
||||||
self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
|
self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
|
||||||
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("profileprefs"))
|
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("profileprefs"))
|
||||||
self.silentlyClose = True
|
self.silentlyClose = True
|
||||||
|
self.prefs = self.mw.col.backend.get_preferences()
|
||||||
self.setupLang()
|
self.setupLang()
|
||||||
self.setupCollection()
|
self.setupCollection()
|
||||||
self.setupNetwork()
|
self.setupNetwork()
|
||||||
|
|
@ -81,25 +79,31 @@ class Preferences(QDialog):
|
||||||
|
|
||||||
f = self.form
|
f = self.form
|
||||||
qc = self.mw.col.conf
|
qc = self.mw.col.conf
|
||||||
self._setupDayCutoff()
|
|
||||||
if isMac:
|
if isMac:
|
||||||
f.hwAccel.setVisible(False)
|
f.hwAccel.setVisible(False)
|
||||||
else:
|
else:
|
||||||
f.hwAccel.setChecked(self.mw.pm.glMode() != "software")
|
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.addItems(list(c.newCardSchedulingLabels().values()))
|
||||||
f.newSpread.setCurrentIndex(qc["newSpread"])
|
|
||||||
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
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.dayLearnFirst.setVisible(False)
|
||||||
f.new_timezone.setVisible(False)
|
f.new_timezone.setVisible(False)
|
||||||
else:
|
else:
|
||||||
f.newSched.setChecked(True)
|
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):
|
def updateCollection(self):
|
||||||
f = self.form
|
f = self.form
|
||||||
|
|
@ -116,22 +120,22 @@ class Preferences(QDialog):
|
||||||
showInfo(_("Changes will take effect when you restart Anki."))
|
showInfo(_("Changes will take effect when you restart Anki."))
|
||||||
|
|
||||||
qc = d.conf
|
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["addToCur"] = not f.useCurrent.currentIndex()
|
||||||
qc["dayLearnFirst"] = f.dayLearnFirst.isChecked()
|
|
||||||
self._updateDayCutoff()
|
s = self.prefs.sched
|
||||||
if self.mw.col.schedVer() != 1:
|
s.show_remaining_due_counts = f.showProgress.isChecked()
|
||||||
was_enabled = self.mw.col.sched.new_timezone_enabled()
|
s.show_intervals_on_buttons = f.showEstimates.isChecked()
|
||||||
is_enabled = f.new_timezone.isChecked()
|
s.new_review_mix = f.newSpread.currentIndex()
|
||||||
if was_enabled != is_enabled:
|
s.time_limit_secs = f.timeLimit.value() * 60
|
||||||
if is_enabled:
|
s.learn_ahead_secs = f.lrnCutoff.value() * 60
|
||||||
self.mw.col.sched.set_creation_offset()
|
s.day_learn_first = f.dayLearnFirst.isChecked()
|
||||||
else:
|
s.rollover = f.dayOffset.value()
|
||||||
self.mw.col.sched.clear_creation_offset()
|
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())
|
self._updateSchedVer(f.newSched.isChecked())
|
||||||
d.setMod()
|
d.setMod()
|
||||||
|
|
||||||
|
|
@ -157,37 +161,6 @@ class Preferences(QDialog):
|
||||||
else:
|
else:
|
||||||
self.mw.col.changeSchedulerVer(1)
|
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
|
# Network
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,5 @@ check_untyped_defs=true
|
||||||
check_untyped_defs=true
|
check_untyped_defs=true
|
||||||
[mypy-aqt.clayout]
|
[mypy-aqt.clayout]
|
||||||
check_untyped_defs=true
|
check_untyped_defs=true
|
||||||
|
[mypy-aqt.preferences]
|
||||||
|
check_untyped_defs=true
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,11 @@ impl Backend {
|
||||||
self.set_local_mins_west(mins)?;
|
self.set_local_mins_west(mins)?;
|
||||||
pb::Empty {}
|
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)?;
|
let val: JsonValue = serde_json::from_slice(&val)?;
|
||||||
col.set_config(input.key.as_str(), &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 {
|
} else {
|
||||||
Err(AnkiError::invalid_input("no op received"))
|
Err(AnkiError::invalid_input("no op received"))
|
||||||
|
|
@ -1115,6 +1120,14 @@ impl Backend {
|
||||||
fn set_local_mins_west(&self, mins: i32) -> Result<()> {
|
fn set_local_mins_west(&self, mins: i32) -> Result<()> {
|
||||||
self.with_col(|col| col.transact(None, |col| col.set_local_mins_west(mins)))
|
self.with_col(|col| col.transact(None, |col| col.set_local_mins_west(mins)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_preferences(&self) -> Result<pb::Preferences> {
|
||||||
|
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<i64>) -> Vec<NoteID> {
|
fn to_nids(ids: Vec<i64>) -> Vec<NoteID> {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ pub(crate) enum ConfigKey {
|
||||||
SchedulerVersion,
|
SchedulerVersion,
|
||||||
LearnAheadSecs,
|
LearnAheadSecs,
|
||||||
NormalizeNoteText,
|
NormalizeNoteText,
|
||||||
|
ShowRemainingDueCountsInStudy,
|
||||||
|
ShowIntervalsAboveAnswerButtons,
|
||||||
|
NewReviewMix,
|
||||||
|
AnswerTimeLimitSecs,
|
||||||
|
ShowDayLearningCardsFirst,
|
||||||
}
|
}
|
||||||
#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)]
|
#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
|
@ -66,6 +71,11 @@ impl From<ConfigKey> for &'static str {
|
||||||
ConfigKey::SchedulerVersion => "schedVer",
|
ConfigKey::SchedulerVersion => "schedVer",
|
||||||
ConfigKey::LearnAheadSecs => "collapseTime",
|
ConfigKey::LearnAheadSecs => "collapseTime",
|
||||||
ConfigKey::NormalizeNoteText => "normalize_note_text",
|
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())
|
.set_config_value(key.into(), val, self.usn()?, TimestampSecs::now())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_config(&self, key: &str) -> Result<()> {
|
pub(crate) fn remove_config<'a, K>(&self, key: K) -> Result<()>
|
||||||
self.storage.remove_config(key)
|
where
|
||||||
|
K: Into<&'a str>,
|
||||||
|
{
|
||||||
|
self.storage.remove_config(key.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_browser_sort_kind(&self) -> SortKind {
|
pub(crate) fn get_browser_sort_kind(&self) -> SortKind {
|
||||||
|
|
@ -130,6 +143,14 @@ impl Collection {
|
||||||
self.get_config_optional(ConfigKey::CreationOffset)
|
self.get_config_optional(ConfigKey::CreationOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_creation_mins_west(&self, mins: Option<i32>) -> 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<i32> {
|
pub(crate) fn get_local_mins_west(&self) -> Option<i32> {
|
||||||
self.get_config_optional(ConfigKey::LocalOffset)
|
self.get_config_optional(ConfigKey::LocalOffset)
|
||||||
}
|
}
|
||||||
|
|
@ -138,11 +159,15 @@ impl Collection {
|
||||||
self.set_config(ConfigKey::LocalOffset, &mins)
|
self.set_config(ConfigKey::LocalOffset, &mins)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_rollover(&self) -> Option<u8> {
|
pub(crate) fn get_v2_rollover(&self) -> Option<u8> {
|
||||||
self.get_config_optional::<u8, _>(ConfigKey::Rollover)
|
self.get_config_optional::<u8, _>(ConfigKey::Rollover)
|
||||||
.map(|r| r.min(23))
|
.map(|r| r.min(23))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_v2_rollover(&self, hour: u32) -> Result<()> {
|
||||||
|
self.set_config(ConfigKey::Rollover, &hour)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn get_current_notetype_id(&self) -> Option<NoteTypeID> {
|
pub(crate) fn get_current_notetype_id(&self) -> Option<NoteTypeID> {
|
||||||
self.get_config_optional(ConfigKey::CurrentNoteTypeID)
|
self.get_config_optional(ConfigKey::CurrentNoteTypeID)
|
||||||
|
|
@ -175,11 +200,63 @@ impl Collection {
|
||||||
.unwrap_or(1200)
|
.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.
|
/// This is a stop-gap solution until we can decouple searching from canonical storage.
|
||||||
pub(crate) fn normalize_note_text(&self) -> bool {
|
pub(crate) fn normalize_note_text(&self) -> bool {
|
||||||
self.get_config_optional(ConfigKey::NormalizeNoteText)
|
self.get_config_optional(ConfigKey::NormalizeNoteText)
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_new_review_mix(&self) -> NewReviewMix {
|
||||||
|
match self.get_config_default::<u8, _>(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)]
|
#[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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::SortKind;
|
use super::SortKind;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub mod log;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod notes;
|
pub mod notes;
|
||||||
pub mod notetype;
|
pub mod notetype;
|
||||||
|
mod preferences;
|
||||||
pub mod sched;
|
pub mod sched;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
|
||||||
85
rslib/src/preferences.rs
Normal file
85
rslib/src/preferences.rs
Normal file
|
|
@ -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<Preferences> {
|
||||||
|
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<CollectionSchedulingSettings> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
fn sched_timing_today_v1(crt: i64, now: i64) -> SchedTimingToday {
|
||||||
let days_elapsed = (now - crt) / 86_400;
|
let days_elapsed = (now - crt) / 86_400;
|
||||||
let next_day_at = crt + (days_elapsed + 1) * 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),
|
v1_creation_date_inner(now, AEST_MINS_WEST),
|
||||||
offset.ymd(2020, 05, 9).and_hms(4, 0, 0).timestamp()
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ use crate::{
|
||||||
pub mod cutoff;
|
pub mod cutoff;
|
||||||
pub mod timespan;
|
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 {
|
impl Collection {
|
||||||
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
|
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
|
||||||
|
|
@ -27,7 +30,7 @@ impl Collection {
|
||||||
now,
|
now,
|
||||||
self.get_creation_mins_west(),
|
self.get_creation_mins_west(),
|
||||||
local_offset,
|
local_offset,
|
||||||
self.get_rollover(),
|
self.get_v2_rollover(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +39,20 @@ impl Collection {
|
||||||
SchedulerVersion::V1 => Ok(v1_rollover_from_creation_stamp(
|
SchedulerVersion::V1 => Ok(v1_rollover_from_creation_stamp(
|
||||||
self.storage.creation_stamp()?.0,
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,13 @@ impl SqliteStorage {
|
||||||
.map_err(Into::into)
|
.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<()> {
|
pub(crate) fn set_schema_modified(&self) -> Result<()> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached("update col set scm = ?")?
|
.prepare_cached("update col set scm = ?")?
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue