// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html mod schema11; mod update; use crate::{ collection::Collection, define_newtype, error::{AnkiError, Result}, scheduler::states::review::INITIAL_EASE_FACTOR, timestamp::{TimestampMillis, TimestampSecs}, types::Usn, }; pub use crate::backend_proto::{ deck_config::config::{LeechAction, NewCardOrder, ReviewCardOrder, ReviewMix}, deck_config::Config as DeckConfigInner, }; pub use schema11::{DeckConfSchema11, NewCardOrderSchema11}; /// Old deck config and cards table store 250% as 2500. pub(crate) const INITIAL_EASE_FACTOR_THOUSANDS: u16 = (INITIAL_EASE_FACTOR * 1000.0) as u16; define_newtype!(DeckConfId, i64); #[derive(Debug, PartialEq, Clone)] pub struct DeckConf { pub id: DeckConfId, pub name: String, pub mtime_secs: TimestampSecs, pub usn: Usn, pub inner: DeckConfigInner, } impl Default for DeckConf { fn default() -> Self { DeckConf { id: DeckConfId(0), name: "".to_string(), mtime_secs: Default::default(), usn: Default::default(), inner: DeckConfigInner { learn_steps: vec![1.0, 10.0], relearn_steps: vec![10.0], new_per_day: 20, reviews_per_day: 200, new_per_day_minimum: 0, initial_ease: 2.5, easy_multiplier: 1.3, hard_multiplier: 1.2, lapse_multiplier: 0.0, interval_multiplier: 1.0, maximum_review_interval: 36_500, minimum_lapse_interval: 1, graduating_interval_good: 1, graduating_interval_easy: 4, new_card_order: NewCardOrder::Due as i32, review_order: ReviewCardOrder::ShuffledByDay as i32, new_mix: ReviewMix::MixWithReviews as i32, interday_learning_mix: ReviewMix::MixWithReviews as i32, leech_action: LeechAction::TagOnly as i32, leech_threshold: 8, disable_autoplay: false, cap_answer_time_to_secs: 60, show_timer: false, skip_question_when_replaying_answer: false, bury_new: false, bury_reviews: false, other: vec![], }, } } } impl Collection { /// If fallback is true, guaranteed to return a deck config. pub fn get_deck_config(&self, dcid: DeckConfId, fallback: bool) -> Result> { 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_secs = 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) } }