Anki/rslib/src/deckconf.rs
Damien Elmes 9874b19526 handle decks set to random new order
It probably makes more sense to randomize on queue build in the future,
but for now this imitates the previous Anki behaviour.
2020-05-12 21:13:34 +10:00

246 lines
6.3 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::{
collection::Collection,
define_newtype,
err::{AnkiError, Result},
serde::default_on_invalid,
timestamp::{TimestampMillis, TimestampSecs},
types::Usn,
};
use serde_aux::field_attributes::deserialize_number_from_string;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_tuple::Serialize_tuple;
use std::collections::HashMap;
define_newtype!(DeckConfID, i64);
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DeckConf {
#[serde(deserialize_with = "deserialize_number_from_string")]
pub(crate) id: DeckConfID,
#[serde(rename = "mod", deserialize_with = "deserialize_number_from_string")]
pub(crate) mtime: TimestampSecs,
pub(crate) name: String,
pub(crate) usn: Usn,
max_taken: i32,
autoplay: bool,
#[serde(deserialize_with = "default_on_invalid")]
timer: u8,
#[serde(default)]
replayq: bool,
pub(crate) new: NewConf,
pub(crate) rev: RevConf,
pub(crate) lapse: LapseConf,
#[serde(flatten)]
other: HashMap<String, Value>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NewConf {
#[serde(default)]
bury: bool,
#[serde(deserialize_with = "default_on_invalid")]
delays: Vec<f32>,
initial_factor: u16,
#[serde(deserialize_with = "default_on_invalid")]
ints: NewCardIntervals,
#[serde(deserialize_with = "default_on_invalid")]
pub(crate) order: NewCardOrder,
#[serde(deserialize_with = "default_on_invalid")]
pub(crate) per_day: u32,
#[serde(flatten)]
other: HashMap<String, Value>,
}
#[derive(Serialize_tuple, Deserialize, Debug, PartialEq, Clone)]
pub struct NewCardIntervals {
good: u16,
easy: u16,
_unused: u16,
}
impl Default for NewCardIntervals {
fn default() -> Self {
Self {
good: 1,
easy: 4,
_unused: 7,
}
}
}
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone)]
#[repr(u8)]
pub enum NewCardOrder {
Random = 0,
Due = 1,
}
impl Default for NewCardOrder {
fn default() -> Self {
Self::Due
}
}
fn hard_factor_default() -> f32 {
1.2
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RevConf {
#[serde(default)]
bury: bool,
ease4: f32,
ivl_fct: f32,
max_ivl: u32,
#[serde(deserialize_with = "default_on_invalid")]
pub(crate) per_day: u32,
#[serde(default = "hard_factor_default")]
hard_factor: f32,
#[serde(flatten)]
other: HashMap<String, Value>,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone)]
#[repr(u8)]
pub enum LeechAction {
Suspend = 0,
TagOnly = 1,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LapseConf {
#[serde(deserialize_with = "default_on_invalid")]
delays: Vec<f32>,
#[serde(deserialize_with = "default_on_invalid")]
leech_action: LeechAction,
leech_fails: u32,
min_int: u32,
mult: f32,
#[serde(flatten)]
other: HashMap<String, Value>,
}
impl Default for LeechAction {
fn default() -> Self {
LeechAction::Suspend
}
}
impl Default for RevConf {
fn default() -> Self {
RevConf {
bury: false,
ease4: 1.3,
ivl_fct: 1.0,
max_ivl: 36500,
per_day: 200,
hard_factor: 1.2,
other: Default::default(),
}
}
}
impl Default for NewConf {
fn default() -> Self {
NewConf {
bury: false,
delays: vec![1.0, 10.0],
initial_factor: 2500,
ints: NewCardIntervals::default(),
order: NewCardOrder::default(),
per_day: 20,
other: Default::default(),
}
}
}
impl Default for LapseConf {
fn default() -> Self {
LapseConf {
delays: vec![10.0],
leech_action: LeechAction::default(),
leech_fails: 8,
min_int: 1,
mult: 0.0,
other: Default::default(),
}
}
}
impl Default for DeckConf {
fn default() -> Self {
DeckConf {
id: DeckConfID(0),
mtime: TimestampSecs(0),
name: "Default".to_string(),
usn: Usn(0),
max_taken: 60,
autoplay: true,
timer: 0,
replayq: true,
new: Default::default(),
rev: Default::default(),
lapse: Default::default(),
other: Default::default(),
}
}
}
impl Collection {
/// If fallback is true, guaranteed to return a deck config.
pub fn get_deck_config(&self, dcid: DeckConfID, fallback: bool) -> Result<Option<DeckConf>> {
if let Some(conf) = self.storage.get_deck_config(dcid)? {
return Ok(Some(conf));
}
if fallback {
if let Some(conf) = self.storage.get_deck_config(DeckConfID(1))? {
return Ok(Some(conf));
}
// if even the default deck config is missing, just return the defaults
Ok(Some(DeckConf::default()))
} else {
Ok(None)
}
}
pub(crate) fn add_or_update_deck_config(
&self,
conf: &mut DeckConf,
preserve_usn_and_mtime: bool,
) -> Result<()> {
if !preserve_usn_and_mtime {
conf.mtime = TimestampSecs::now();
conf.usn = self.usn()?;
}
let orig = self.storage.get_deck_config(conf.id)?;
if let Some(_orig) = orig {
self.storage.update_deck_conf(&conf)
} else {
if conf.id.0 == 0 {
conf.id.0 = TimestampMillis::now().0;
}
self.storage.add_deck_conf(conf)
}
}
/// Remove a deck configuration. This will force a full sync.
pub(crate) fn remove_deck_config(&self, dcid: DeckConfID) -> Result<()> {
if dcid.0 == 1 {
return Err(AnkiError::invalid_input("can't delete default conf"));
}
self.storage.set_schema_modified()?;
self.storage.remove_deck_conf(dcid)
}
}