mirror of
https://github.com/ankitects/anki.git
synced 2025-11-12 23:57:13 -05:00
Instead of throwing an error if schema not marked as changed, just mark it changed, as that way it can be included as part of the same transaction.
245 lines
6.3 KiB
Rust
245 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")]
|
|
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 {
|
|
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)
|
|
}
|
|
}
|