250 lines
6.3 KiB
Rust
250 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_bool_from_anything, 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 = "deserialize_bool_from_anything")]
|
|
timer: bool,
|
|
#[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")]
|
|
order: NewCardOrder,
|
|
#[serde(deserialize_with = "default_on_invalid")]
|
|
pub(crate) per_day: u32,
|
|
|
|
// unused, can remove in the future
|
|
#[serde(default)]
|
|
separate: bool,
|
|
|
|
#[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,
|
|
separate: true,
|
|
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: false,
|
|
replayq: true,
|
|
new: Default::default(),
|
|
rev: Default::default(),
|
|
lapse: Default::default(),
|
|
other: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Collection {
|
|
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)
|
|
}
|
|
}
|
|
|
|
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.ensure_schema_modified()?;
|
|
self.storage.remove_deck_conf(dcid)
|
|
}
|
|
}
|