From 76acf04dc06f1fd04e33c53d0bf13228e7eeca44 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 22 Sep 2020 08:16:39 +1000 Subject: [PATCH] update schema to fix default initial ease in deck configs Closes #766 - changes the on-disk representation from % to a multiplier, eg 250 -> 2.5, as this is consistent with the other options - resets deck configs at or below 1.3 to 2.5 - for any cards that were using a reset deck config, reset their current factor if it's at or below 2.0x. The cutoff is arbitrary, and just intended to make sure we catch cards the user has rated Easy on multiple times. The existing due dates are left alone. --- rslib/src/deckconf/mod.rs | 3 +- rslib/src/deckconf/schema11.rs | 8 +++--- rslib/src/sched/learning.rs | 4 +-- rslib/src/sched/new.rs | 4 +-- rslib/src/sched/reviews.rs | 4 +-- rslib/src/storage/card/fix_low_ease.sql | 10 +++++++ rslib/src/storage/card/mod.rs | 37 ++++++++++++++++++++++++- rslib/src/storage/deckconf/mod.rs | 24 +++++++++++++++- rslib/src/storage/sqlite.rs | 4 +-- rslib/src/storage/upgrades/mod.rs | 11 ++++++++ 10 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 rslib/src/storage/card/fix_low_ease.sql 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(()) }