diff --git a/rslib/src/deckconf/mod.rs b/rslib/src/deckconf/mod.rs index 4235acbfa..4b909c03a 100644 --- a/rslib/src/deckconf/mod.rs +++ b/rslib/src/deckconf/mod.rs @@ -14,7 +14,8 @@ pub use crate::backend_proto::{ DeckConfigInner, }; pub use schema11::{DeckConfSchema11, NewCardOrderSchema11}; -pub const INITIAL_EASE_FACTOR: u16 = 2500; +/// Old deck config and cards table store 250% as 2500. +pub const INITIAL_EASE_FACTOR_THOUSANDS: u16 = 2500; mod schema11; diff --git a/rslib/src/deckconf/schema11.rs b/rslib/src/deckconf/schema11.rs index 5c9e0039e..411ec86e4 100644 --- a/rslib/src/deckconf/schema11.rs +++ b/rslib/src/deckconf/schema11.rs @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use super::{DeckConf, DeckConfID, INITIAL_EASE_FACTOR}; +use super::{DeckConf, DeckConfID, INITIAL_EASE_FACTOR_THOUSANDS}; use crate::backend_proto::deck_config_inner::NewCardOrder; use crate::backend_proto::DeckConfigInner; use crate::{serde::default_on_invalid, timestamp::TimestampSecs, types::Usn}; @@ -153,7 +153,7 @@ impl Default for NewConfSchema11 { NewConfSchema11 { bury: false, delays: vec![1.0, 10.0], - initial_factor: INITIAL_EASE_FACTOR, + initial_factor: INITIAL_EASE_FACTOR_THOUSANDS, ints: NewCardIntervals::default(), order: NewCardOrderSchema11::default(), per_day: 20, @@ -237,7 +237,7 @@ impl From for DeckConf { reviews_per_day: c.rev.per_day, bury_new: c.new.bury, bury_reviews: c.rev.bury, - initial_ease: (c.new.initial_factor as f32) / 10.0, + initial_ease: (c.new.initial_factor as f32) / 1000.0, easy_multiplier: c.rev.ease4, hard_multiplier: c.rev.hard_factor, lapse_multiplier: c.lapse.mult, @@ -298,7 +298,7 @@ impl From for DeckConfSchema11 { new: NewConfSchema11 { bury: i.bury_new, delays: i.learn_steps, - initial_factor: (i.initial_ease * 10.0) as u16, + initial_factor: (i.initial_ease * 1000.0) as u16, ints: NewCardIntervals { good: i.graduating_interval_good as u16, easy: i.graduating_interval_easy as u16, diff --git a/rslib/src/sched/learning.rs b/rslib/src/sched/learning.rs index 80f797bfa..10ee26f61 100644 --- a/rslib/src/sched/learning.rs +++ b/rslib/src/sched/learning.rs @@ -3,7 +3,7 @@ use crate::{ card::{Card, CardQueue, CardType}, - deckconf::INITIAL_EASE_FACTOR, + deckconf::INITIAL_EASE_FACTOR_THOUSANDS, }; impl Card { @@ -29,7 +29,7 @@ impl Card { self.interval = 0; self.due = 0; self.original_due = 0; - self.ease_factor = INITIAL_EASE_FACTOR; + self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS; } } diff --git a/rslib/src/sched/new.rs b/rslib/src/sched/new.rs index dc7bb9e68..e85c79ddc 100644 --- a/rslib/src/sched/new.rs +++ b/rslib/src/sched/new.rs @@ -4,7 +4,7 @@ use crate::{ card::{Card, CardID, CardQueue, CardType}, collection::Collection, - deckconf::INITIAL_EASE_FACTOR, + deckconf::INITIAL_EASE_FACTOR_THOUSANDS, decks::DeckID, err::Result, notes::NoteID, @@ -24,7 +24,7 @@ impl Card { if self.ease_factor == 0 { // unlike the old Python code, we leave the ease factor alone // if it's already set - self.ease_factor = INITIAL_EASE_FACTOR; + self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS; } } diff --git a/rslib/src/sched/reviews.rs b/rslib/src/sched/reviews.rs index 182cbe08e..dd6d95150 100644 --- a/rslib/src/sched/reviews.rs +++ b/rslib/src/sched/reviews.rs @@ -4,7 +4,7 @@ use crate::{ card::{Card, CardID, CardQueue, CardType}, collection::Collection, - deckconf::INITIAL_EASE_FACTOR, + deckconf::INITIAL_EASE_FACTOR_THOUSANDS, err::Result, }; use rand::distributions::{Distribution, Uniform}; @@ -19,7 +19,7 @@ impl Card { if self.ease_factor == 0 { // unlike the old Python code, we leave the ease factor alone // if it's already set - self.ease_factor = INITIAL_EASE_FACTOR; + self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS; } } } diff --git a/rslib/src/storage/card/fix_low_ease.sql b/rslib/src/storage/card/fix_low_ease.sql new file mode 100644 index 000000000..d905613d0 --- /dev/null +++ b/rslib/src/storage/card/fix_low_ease.sql @@ -0,0 +1,10 @@ +update cards +set factor = 2500, + usn = ?, + mod = ? +where factor != 0 + and factor <= 2000 + and ( + did in DECK_IDS + or odid in DECK_IDS + ) \ No newline at end of file diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index f001e1f3b..f8dee7328 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -3,7 +3,8 @@ use crate::{ card::{Card, CardID, CardQueue, CardType}, - decks::{Deck, DeckID}, + deckconf::DeckConfID, + decks::{Deck, DeckID, DeckKind}, err::Result, notes::NoteID, sched::congrats::CongratsInfo, @@ -18,6 +19,8 @@ use rusqlite::{ }; use std::{collections::HashSet, convert::TryFrom, result}; +use super::ids_to_string; + impl FromSql for CardType { fn column_result(value: ValueRef<'_>) -> std::result::Result { if let ValueRef::Integer(i) = value { @@ -350,6 +353,38 @@ impl super::SqliteStorage { Ok(()) } + + /// Fix cards with low eases due to schema 15 bug. + /// Deck configs were defaulting to 2.5% ease, which was capped to + /// 130% when the deck options were edited for the first time. + pub(crate) fn fix_low_card_eases_for_configs( + &self, + configs: &[DeckConfID], + server: bool, + ) -> Result<()> { + let mut affected_decks = vec![]; + for conf in configs { + for (deck_id, _name) in self.get_all_deck_names()? { + if let Some(deck) = self.get_deck(deck_id)? { + if let DeckKind::Normal(normal) = &deck.kind { + if normal.config_id == conf.0 { + affected_decks.push(deck.id); + } + } + } + } + } + + let mut ids = String::new(); + ids_to_string(&mut ids, &affected_decks); + let sql = include_str!("fix_low_ease.sql").replace("DECK_IDS", &ids); + + self.db + .prepare(&sql)? + .execute(params![self.usn(server)?, TimestampSecs::now()])?; + + Ok(()) + } } #[cfg(test)] diff --git a/rslib/src/storage/deckconf/mod.rs b/rslib/src/storage/deckconf/mod.rs index 11cde03ec..9c9e150f3 100644 --- a/rslib/src/storage/deckconf/mod.rs +++ b/rslib/src/storage/deckconf/mod.rs @@ -159,13 +159,35 @@ impl SqliteStorage { pub(super) fn upgrade_deck_conf_to_schema15(&self) -> Result<()> { for conf in self.all_deck_config_schema14()? { - let conf: DeckConf = conf.into(); + let mut conf: DeckConf = conf.into(); + // schema 15 stored starting ease of 2.5 as 250 + conf.inner.initial_ease *= 100.0; self.update_deck_conf(&conf)?; } Ok(()) } + // schema 15->16 + + pub(super) fn upgrade_deck_conf_to_schema16(&self, server: bool) -> Result<()> { + let mut invalid_configs = vec![]; + for mut conf in self.all_deck_config()? { + // schema 16 changed starting ease of 250 to 2.5 + conf.inner.initial_ease /= 100.0; + // new deck configs created with schema 15 had the wrong + // ease set - reset any deck configs at the minimum ease + // to the default 250% + if conf.inner.initial_ease <= 1.3 { + conf.inner.initial_ease = 2.5; + invalid_configs.push(conf.id); + } + self.update_deck_conf(&conf)?; + } + + self.fix_low_card_eases_for_configs(&invalid_configs, server) + } + // schema 15->11 pub(super) fn downgrade_deck_conf_from_schema15(&self) -> Result<()> { diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index 34d385b13..36dc0c05b 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -12,9 +12,7 @@ use std::cmp::Ordering; use std::{borrow::Cow, path::Path, sync::Arc}; use unicase::UniCase; -const SCHEMA_MIN_VERSION: u8 = 11; -const SCHEMA_STARTING_VERSION: u8 = 11; -const SCHEMA_MAX_VERSION: u8 = 15; +use super::upgrades::{SCHEMA_MAX_VERSION, SCHEMA_MIN_VERSION, SCHEMA_STARTING_VERSION}; fn unicase_compare(s1: &str, s2: &str) -> Ordering { UniCase::new(s1).cmp(&UniCase::new(s2)) diff --git a/rslib/src/storage/upgrades/mod.rs b/rslib/src/storage/upgrades/mod.rs index e84f2c570..ae53cb40e 100644 --- a/rslib/src/storage/upgrades/mod.rs +++ b/rslib/src/storage/upgrades/mod.rs @@ -1,6 +1,13 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +/// The minimum schema version we can open. +pub(super) const SCHEMA_MIN_VERSION: u8 = 11; +/// The version new files are initially created with. +pub(super) const SCHEMA_STARTING_VERSION: u8 = 11; +/// The maximum schema version we can open. +pub(super) const SCHEMA_MAX_VERSION: u8 = 16; + use super::SqliteStorage; use crate::err::Result; @@ -20,6 +27,10 @@ impl SqliteStorage { self.upgrade_decks_to_schema15(server)?; self.upgrade_deck_conf_to_schema15()?; } + if ver < 16 { + self.upgrade_deck_conf_to_schema16(server)?; + self.db.execute_batch("update col set ver = 16")?; + } Ok(()) }