mirror of
https://github.com/ankitects/anki.git
synced 2025-12-13 23:00:58 -05:00
move deck config to protobuf
This commit is contained in:
parent
54670580ad
commit
6114836484
11 changed files with 596 additions and 280 deletions
|
|
@ -793,3 +793,50 @@ message GetDeckNamesIn {
|
||||||
// if unset, implies skip_empty_default
|
// if unset, implies skip_empty_default
|
||||||
bool include_filtered = 2;
|
bool include_filtered = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DeckConfigInner {
|
||||||
|
enum NewCardOrder {
|
||||||
|
NEW_CARD_ORDER_DUE = 0;
|
||||||
|
NEW_CARD_ORDER_RANDOM = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LeechAction {
|
||||||
|
LEECH_ACTION_SUSPEND = 0;
|
||||||
|
LEECH_ACTION_TAG_ONLY = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated float learn_steps = 1;
|
||||||
|
repeated float relearn_steps = 2;
|
||||||
|
|
||||||
|
reserved 3 to 8;
|
||||||
|
|
||||||
|
uint32 new_per_day = 9;
|
||||||
|
uint32 reviews_per_day = 10;
|
||||||
|
|
||||||
|
float initial_ease = 11;
|
||||||
|
float easy_multiplier = 12;
|
||||||
|
float hard_multiplier = 13;
|
||||||
|
float lapse_multiplier = 14;
|
||||||
|
float interval_multiplier = 15;
|
||||||
|
|
||||||
|
uint32 maximum_review_interval = 16;
|
||||||
|
uint32 minimum_review_interval = 17;
|
||||||
|
|
||||||
|
uint32 graduating_interval_good = 18;
|
||||||
|
uint32 graduating_interval_easy = 19;
|
||||||
|
|
||||||
|
NewCardOrder new_card_order = 20;
|
||||||
|
|
||||||
|
LeechAction leech_action = 21;
|
||||||
|
uint32 leech_threshold = 22;
|
||||||
|
|
||||||
|
bool disable_autoplay = 23;
|
||||||
|
uint32 cap_answer_time_to_secs = 24;
|
||||||
|
uint32 visible_timer_secs = 25;
|
||||||
|
bool skip_question_when_replaying_answer = 26;
|
||||||
|
|
||||||
|
bool bury_new = 27;
|
||||||
|
bool bury_reviews = 28;
|
||||||
|
|
||||||
|
bytes other = 255;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -730,7 +730,11 @@ class RustBackend:
|
||||||
|
|
||||||
def deck_tree(self, include_counts: bool, top_deck_id: int = 0) -> DeckTreeNode:
|
def deck_tree(self, include_counts: bool, top_deck_id: int = 0) -> DeckTreeNode:
|
||||||
return self._run_command(
|
return self._run_command(
|
||||||
pb.BackendInput(deck_tree=pb.DeckTreeIn(include_counts=include_counts, top_deck_id=top_deck_id))
|
pb.BackendInput(
|
||||||
|
deck_tree=pb.DeckTreeIn(
|
||||||
|
include_counts=include_counts, top_deck_id=top_deck_id
|
||||||
|
)
|
||||||
|
)
|
||||||
).deck_tree
|
).deck_tree
|
||||||
|
|
||||||
def check_database(self) -> List[str]:
|
def check_database(self) -> List[str]:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
cloze::add_cloze_numbers_in_string,
|
cloze::add_cloze_numbers_in_string,
|
||||||
collection::{open_collection, Collection},
|
collection::{open_collection, Collection},
|
||||||
config::SortKind,
|
config::SortKind,
|
||||||
deckconf::{DeckConf, DeckConfID},
|
deckconf::{DeckConf, DeckConfID, DeckConfSchema11},
|
||||||
decks::{Deck, DeckID, DeckSchema11},
|
decks::{Deck, DeckID, DeckSchema11},
|
||||||
err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind},
|
err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind},
|
||||||
i18n::{tr_args, I18n, TR},
|
i18n::{tr_args, I18n, TR},
|
||||||
|
|
@ -758,12 +758,14 @@ impl Backend {
|
||||||
fn get_deck_config(&self, dcid: i64) -> Result<Vec<u8>> {
|
fn get_deck_config(&self, dcid: i64) -> Result<Vec<u8>> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
let conf = col.get_deck_config(DeckConfID(dcid), true)?.unwrap();
|
let conf = col.get_deck_config(DeckConfID(dcid), true)?.unwrap();
|
||||||
|
let conf: DeckConfSchema11 = conf.into();
|
||||||
Ok(serde_json::to_vec(&conf)?)
|
Ok(serde_json::to_vec(&conf)?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_or_update_deck_config(&self, input: AddOrUpdateDeckConfigIn) -> Result<i64> {
|
fn add_or_update_deck_config(&self, input: AddOrUpdateDeckConfigIn) -> Result<i64> {
|
||||||
let mut conf: DeckConf = serde_json::from_slice(&input.config)?;
|
let conf: DeckConfSchema11 = serde_json::from_slice(&input.config)?;
|
||||||
|
let mut conf: DeckConf = conf.into();
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
col.transact(None, |col| {
|
col.transact(None, |col| {
|
||||||
col.add_or_update_deck_config(&mut conf, input.preserve_usn_and_mtime)?;
|
col.add_or_update_deck_config(&mut conf, input.preserve_usn_and_mtime)?;
|
||||||
|
|
@ -773,11 +775,19 @@ impl Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_deck_config(&self) -> Result<Vec<u8>> {
|
fn all_deck_config(&self) -> Result<Vec<u8>> {
|
||||||
self.with_col(|col| serde_json::to_vec(&col.storage.all_deck_config()?).map_err(Into::into))
|
self.with_col(|col| {
|
||||||
|
let conf: Vec<DeckConfSchema11> = col
|
||||||
|
.storage
|
||||||
|
.all_deck_config()?
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_vec(&conf).map_err(Into::into)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_deck_config(&self) -> Result<Vec<u8>> {
|
fn new_deck_config(&self) -> Result<Vec<u8>> {
|
||||||
serde_json::to_vec(&DeckConf::default()).map_err(Into::into)
|
serde_json::to_vec(&DeckConfSchema11::default()).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_deck_config(&self, dcid: i64) -> Result<()> {
|
fn remove_deck_config(&self, dcid: i64) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
// 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")]
|
|
||||||
pub(crate) 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 {
|
|
||||||
/// If fallback is true, guaranteed to return a deck config.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
112
rslib/src/deckconf/mod.rs
Normal file
112
rslib/src/deckconf/mod.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
// 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},
|
||||||
|
timestamp::{TimestampMillis, TimestampSecs},
|
||||||
|
types::Usn,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use crate::backend_proto::{
|
||||||
|
deck_config_inner::{LeechAction, NewCardOrder},
|
||||||
|
DeckConfigInner,
|
||||||
|
};
|
||||||
|
pub use schema11::{DeckConfSchema11, NewCardOrderSchema11};
|
||||||
|
|
||||||
|
mod schema11;
|
||||||
|
|
||||||
|
define_newtype!(DeckConfID, i64);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
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],
|
||||||
|
disable_autoplay: false,
|
||||||
|
cap_answer_time_to_secs: 60,
|
||||||
|
visible_timer_secs: 0,
|
||||||
|
skip_question_when_replaying_answer: false,
|
||||||
|
new_per_day: 20,
|
||||||
|
reviews_per_day: 200,
|
||||||
|
bury_new: false,
|
||||||
|
bury_reviews: false,
|
||||||
|
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_review_interval: 1,
|
||||||
|
graduating_interval_good: 1,
|
||||||
|
graduating_interval_easy: 4,
|
||||||
|
new_card_order: NewCardOrder::Due as i32,
|
||||||
|
leech_action: LeechAction::Suspend as i32,
|
||||||
|
leech_threshold: 8,
|
||||||
|
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<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_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
329
rslib/src/deckconf/schema11.rs
Normal file
329
rslib/src/deckconf/schema11.rs
Normal file
|
|
@ -0,0 +1,329 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use super::{DeckConf, DeckConfID};
|
||||||
|
use crate::backend_proto::deck_config_inner::NewCardOrder;
|
||||||
|
use crate::backend_proto::DeckConfigInner;
|
||||||
|
use crate::{serde::default_on_invalid, timestamp::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;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DeckConfSchema11 {
|
||||||
|
#[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: NewConfSchema11,
|
||||||
|
pub(crate) rev: RevConfSchema11,
|
||||||
|
pub(crate) lapse: LapseConfSchema11,
|
||||||
|
#[serde(flatten)]
|
||||||
|
other: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NewConfSchema11 {
|
||||||
|
#[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")]
|
||||||
|
pub(crate) order: NewCardOrderSchema11,
|
||||||
|
#[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 NewCardOrderSchema11 {
|
||||||
|
Random = 0,
|
||||||
|
Due = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NewCardOrderSchema11 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Due
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hard_factor_default() -> f32 {
|
||||||
|
1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RevConfSchema11 {
|
||||||
|
#[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 LapseConfSchema11 {
|
||||||
|
#[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 RevConfSchema11 {
|
||||||
|
fn default() -> Self {
|
||||||
|
RevConfSchema11 {
|
||||||
|
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 NewConfSchema11 {
|
||||||
|
fn default() -> Self {
|
||||||
|
NewConfSchema11 {
|
||||||
|
bury: false,
|
||||||
|
delays: vec![1.0, 10.0],
|
||||||
|
initial_factor: 2500,
|
||||||
|
ints: NewCardIntervals::default(),
|
||||||
|
order: NewCardOrderSchema11::default(),
|
||||||
|
per_day: 20,
|
||||||
|
other: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LapseConfSchema11 {
|
||||||
|
fn default() -> Self {
|
||||||
|
LapseConfSchema11 {
|
||||||
|
delays: vec![10.0],
|
||||||
|
leech_action: LeechAction::default(),
|
||||||
|
leech_fails: 8,
|
||||||
|
min_int: 1,
|
||||||
|
mult: 0.0,
|
||||||
|
other: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DeckConfSchema11 {
|
||||||
|
fn default() -> Self {
|
||||||
|
DeckConfSchema11 {
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema11 -> schema15
|
||||||
|
|
||||||
|
impl From<DeckConfSchema11> for DeckConf {
|
||||||
|
fn from(mut c: DeckConfSchema11) -> DeckConf {
|
||||||
|
// merge any json stored in new/rev/lapse into top level
|
||||||
|
if !c.new.other.is_empty() {
|
||||||
|
if let Ok(val) = serde_json::to_value(c.new.other) {
|
||||||
|
c.other.insert("new".into(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !c.rev.other.is_empty() {
|
||||||
|
if let Ok(val) = serde_json::to_value(c.rev.other) {
|
||||||
|
c.other.insert("rev".into(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !c.lapse.other.is_empty() {
|
||||||
|
if let Ok(val) = serde_json::to_value(c.lapse.other) {
|
||||||
|
c.other.insert("lapse".into(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let other_bytes = serde_json::to_vec(&c.other).unwrap_or_default();
|
||||||
|
|
||||||
|
DeckConf {
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
mtime_secs: c.mtime,
|
||||||
|
usn: c.usn,
|
||||||
|
inner: DeckConfigInner {
|
||||||
|
learn_steps: c.new.delays,
|
||||||
|
relearn_steps: c.lapse.delays,
|
||||||
|
disable_autoplay: !c.autoplay,
|
||||||
|
cap_answer_time_to_secs: c.max_taken.max(0) as u32,
|
||||||
|
visible_timer_secs: c.timer as u32,
|
||||||
|
skip_question_when_replaying_answer: !c.replayq,
|
||||||
|
new_per_day: c.new.per_day,
|
||||||
|
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,
|
||||||
|
easy_multiplier: c.rev.ease4,
|
||||||
|
hard_multiplier: c.rev.hard_factor,
|
||||||
|
lapse_multiplier: c.lapse.mult,
|
||||||
|
interval_multiplier: c.rev.ivl_fct,
|
||||||
|
maximum_review_interval: c.rev.max_ivl,
|
||||||
|
minimum_review_interval: c.lapse.min_int,
|
||||||
|
graduating_interval_good: c.new.ints.good as u32,
|
||||||
|
graduating_interval_easy: c.new.ints.easy as u32,
|
||||||
|
new_card_order: match c.new.order {
|
||||||
|
NewCardOrderSchema11::Random => NewCardOrder::Random,
|
||||||
|
NewCardOrderSchema11::Due => NewCardOrder::Due,
|
||||||
|
} as i32,
|
||||||
|
leech_action: c.lapse.leech_action as i32,
|
||||||
|
leech_threshold: c.lapse.leech_fails,
|
||||||
|
other: other_bytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema 15 -> schema 11
|
||||||
|
impl From<DeckConf> for DeckConfSchema11 {
|
||||||
|
fn from(c: DeckConf) -> DeckConfSchema11 {
|
||||||
|
// split extra json up
|
||||||
|
let mut top_other: HashMap<String, Value>;
|
||||||
|
let mut new_other = Default::default();
|
||||||
|
let mut rev_other = Default::default();
|
||||||
|
let mut lapse_other = Default::default();
|
||||||
|
if c.inner.other.is_empty() {
|
||||||
|
top_other = Default::default();
|
||||||
|
} else {
|
||||||
|
top_other = serde_json::from_slice(&c.inner.other).unwrap_or_default();
|
||||||
|
if let Some(new) = top_other.remove("new") {
|
||||||
|
let val: HashMap<String, Value> = serde_json::from_value(new).unwrap_or_default();
|
||||||
|
new_other = val;
|
||||||
|
}
|
||||||
|
if let Some(rev) = top_other.remove("rev") {
|
||||||
|
let val: HashMap<String, Value> = serde_json::from_value(rev).unwrap_or_default();
|
||||||
|
rev_other = val;
|
||||||
|
}
|
||||||
|
if let Some(lapse) = top_other.remove("lapse") {
|
||||||
|
let val: HashMap<String, Value> = serde_json::from_value(lapse).unwrap_or_default();
|
||||||
|
lapse_other = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = c.inner;
|
||||||
|
let new_order = i.new_card_order();
|
||||||
|
DeckConfSchema11 {
|
||||||
|
id: c.id,
|
||||||
|
mtime: c.mtime_secs,
|
||||||
|
name: c.name,
|
||||||
|
usn: c.usn,
|
||||||
|
max_taken: i.cap_answer_time_to_secs as i32,
|
||||||
|
autoplay: !i.disable_autoplay,
|
||||||
|
timer: i.visible_timer_secs as u8,
|
||||||
|
replayq: !i.skip_question_when_replaying_answer,
|
||||||
|
new: NewConfSchema11 {
|
||||||
|
bury: i.bury_new,
|
||||||
|
delays: i.learn_steps,
|
||||||
|
initial_factor: (i.initial_ease * 10.0) as u16,
|
||||||
|
ints: NewCardIntervals {
|
||||||
|
good: i.graduating_interval_good as u16,
|
||||||
|
easy: i.graduating_interval_easy as u16,
|
||||||
|
_unused: 0,
|
||||||
|
},
|
||||||
|
order: match new_order {
|
||||||
|
NewCardOrder::Random => NewCardOrderSchema11::Random,
|
||||||
|
NewCardOrder::Due => NewCardOrderSchema11::Due,
|
||||||
|
},
|
||||||
|
per_day: i.new_per_day,
|
||||||
|
other: new_other,
|
||||||
|
},
|
||||||
|
rev: RevConfSchema11 {
|
||||||
|
bury: i.bury_reviews,
|
||||||
|
ease4: i.easy_multiplier,
|
||||||
|
ivl_fct: i.interval_multiplier,
|
||||||
|
max_ivl: i.maximum_review_interval,
|
||||||
|
per_day: i.reviews_per_day,
|
||||||
|
hard_factor: i.hard_multiplier,
|
||||||
|
other: rev_other,
|
||||||
|
},
|
||||||
|
lapse: LapseConfSchema11 {
|
||||||
|
delays: i.relearn_steps,
|
||||||
|
leech_action: match i.leech_action {
|
||||||
|
1 => LeechAction::TagOnly,
|
||||||
|
_ => LeechAction::Suspend,
|
||||||
|
},
|
||||||
|
leech_fails: i.leech_threshold,
|
||||||
|
min_int: i.minimum_review_interval,
|
||||||
|
mult: i.lapse_multiplier,
|
||||||
|
other: lapse_other,
|
||||||
|
},
|
||||||
|
other: top_other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,8 +137,12 @@ fn remaining_counts_for_deck(
|
||||||
.get(&DeckConfID(norm.config_id))
|
.get(&DeckConfID(norm.config_id))
|
||||||
.or_else(|| dconf.get(&DeckConfID(1)))
|
.or_else(|| dconf.get(&DeckConfID(1)))
|
||||||
{
|
{
|
||||||
let new = (conf.new.per_day as i32).saturating_sub(new_today).max(0);
|
let new = (conf.inner.new_per_day as i32)
|
||||||
let rev = (conf.rev.per_day as i32).saturating_sub(rev_today).max(0);
|
.saturating_sub(new_today)
|
||||||
|
.max(0);
|
||||||
|
let rev = (conf.inner.reviews_per_day as i32)
|
||||||
|
.saturating_sub(rev_today)
|
||||||
|
.max(0);
|
||||||
(new as u32, rev as u32)
|
(new as u32, rev as u32)
|
||||||
} else {
|
} else {
|
||||||
// missing dconf and fallback
|
// missing dconf and fallback
|
||||||
|
|
@ -342,7 +346,7 @@ mod test {
|
||||||
|
|
||||||
// set the limit to 4, which should mean 3 are left
|
// set the limit to 4, which should mean 3 are left
|
||||||
let mut conf = col.get_deck_config(DeckConfID(1), false)?.unwrap();
|
let mut conf = col.get_deck_config(DeckConfID(1), false)?.unwrap();
|
||||||
conf.new.per_day = 4;
|
conf.inner.new_per_day = 4;
|
||||||
col.add_or_update_deck_config(&mut conf, false)?;
|
col.add_or_update_deck_config(&mut conf, false)?;
|
||||||
|
|
||||||
let tree = col.deck_tree(true, None)?;
|
let tree = col.deck_tree(true, None)?;
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
let next_pos = cache.next_position.unwrap();
|
let next_pos = cache.next_position.unwrap();
|
||||||
|
|
||||||
match cache.deck_configs.get(&did).unwrap().new.order {
|
match cache.deck_configs.get(&did).unwrap().inner.new_card_order() {
|
||||||
crate::deckconf::NewCardOrder::Random => Ok(random_position(next_pos)),
|
crate::deckconf::NewCardOrder::Random => Ok(random_position(next_pos)),
|
||||||
crate::deckconf::NewCardOrder::Due => Ok(next_pos),
|
crate::deckconf::NewCardOrder::Due => Ok(next_pos),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
select
|
select
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
mtime_secs,
|
||||||
|
usn,
|
||||||
config
|
config
|
||||||
from deck_config
|
from deck_config
|
||||||
where
|
|
||||||
id = ?;
|
|
||||||
|
|
@ -3,61 +3,70 @@
|
||||||
|
|
||||||
use super::SqliteStorage;
|
use super::SqliteStorage;
|
||||||
use crate::{
|
use crate::{
|
||||||
deckconf::{DeckConf, DeckConfID},
|
deckconf::{DeckConf, DeckConfID, DeckConfSchema11, DeckConfigInner},
|
||||||
err::Result,
|
err::Result,
|
||||||
i18n::{I18n, TR},
|
i18n::{I18n, TR},
|
||||||
};
|
};
|
||||||
use rusqlite::{params, NO_PARAMS};
|
use prost::Message;
|
||||||
|
use rusqlite::{params, Row, NO_PARAMS};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn row_to_deckconf(row: &Row) -> Result<DeckConf> {
|
||||||
|
let config = DeckConfigInner::decode(row.get_raw(4).as_blob()?)?;
|
||||||
|
Ok(DeckConf {
|
||||||
|
id: row.get(0)?,
|
||||||
|
name: row.get(1)?,
|
||||||
|
mtime_secs: row.get(2)?,
|
||||||
|
usn: row.get(3)?,
|
||||||
|
inner: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl SqliteStorage {
|
impl SqliteStorage {
|
||||||
pub(crate) fn all_deck_config(&self) -> Result<Vec<DeckConf>> {
|
pub(crate) fn all_deck_config(&self) -> Result<Vec<DeckConf>> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached("select config from deck_config")?
|
.prepare_cached(include_str!("get.sql"))?
|
||||||
.query_and_then(NO_PARAMS, |row| -> Result<_> {
|
.query_and_then(NO_PARAMS, row_to_deckconf)?
|
||||||
Ok(serde_json::from_slice(row.get_raw(0).as_blob()?)?)
|
|
||||||
})?
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_deck_config(&self, dcid: DeckConfID) -> Result<Option<DeckConf>> {
|
pub(crate) fn get_deck_config(&self, dcid: DeckConfID) -> Result<Option<DeckConf>> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached(include_str!("get.sql"))?
|
.prepare_cached(concat!(include_str!("get.sql"), " where id = ?"))?
|
||||||
.query_and_then(params![dcid], |row| -> Result<_> {
|
.query_and_then(params![dcid], row_to_deckconf)?
|
||||||
Ok(serde_json::from_slice(row.get_raw(0).as_blob()?)?)
|
|
||||||
})?
|
|
||||||
.next()
|
.next()
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_deck_conf(&self, conf: &mut DeckConf) -> Result<()> {
|
pub(crate) fn add_deck_conf(&self, conf: &mut DeckConf) -> Result<()> {
|
||||||
|
let mut conf_bytes = vec![];
|
||||||
|
conf.inner.encode(&mut conf_bytes)?;
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached(include_str!("add.sql"))?
|
.prepare_cached(include_str!("add.sql"))?
|
||||||
.execute(params![
|
.execute(params![
|
||||||
conf.id,
|
conf.id,
|
||||||
conf.name,
|
conf.name,
|
||||||
conf.mtime,
|
conf.mtime_secs,
|
||||||
conf.usn,
|
conf.usn,
|
||||||
&serde_json::to_vec(conf)?,
|
conf_bytes,
|
||||||
])?;
|
])?;
|
||||||
let id = self.db.last_insert_rowid();
|
let id = self.db.last_insert_rowid();
|
||||||
if conf.id.0 != id {
|
if conf.id.0 != id {
|
||||||
// if the initial ID conflicted, make sure the json is up to date
|
|
||||||
// as well
|
|
||||||
conf.id.0 = id;
|
conf.id.0 = id;
|
||||||
self.update_deck_conf(conf)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_deck_conf(&self, conf: &DeckConf) -> Result<()> {
|
pub(crate) fn update_deck_conf(&self, conf: &DeckConf) -> Result<()> {
|
||||||
|
let mut conf_bytes = vec![];
|
||||||
|
conf.inner.encode(&mut conf_bytes)?;
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached(include_str!("update.sql"))?
|
.prepare_cached(include_str!("update.sql"))?
|
||||||
.execute(params![
|
.execute(params![
|
||||||
conf.name,
|
conf.name,
|
||||||
conf.mtime,
|
conf.mtime_secs,
|
||||||
conf.usn,
|
conf.usn,
|
||||||
&serde_json::to_vec(conf)?,
|
conf_bytes,
|
||||||
conf.id,
|
conf.id,
|
||||||
])?;
|
])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -90,26 +99,70 @@ impl SqliteStorage {
|
||||||
self.add_deck_conf(&mut conf)
|
self.add_deck_conf(&mut conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// schema 11->14
|
||||||
|
|
||||||
|
fn add_deck_conf_schema14(&self, conf: &mut DeckConfSchema11) -> Result<()> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached(include_str!("add.sql"))?
|
||||||
|
.execute(params![
|
||||||
|
conf.id,
|
||||||
|
conf.name,
|
||||||
|
conf.mtime,
|
||||||
|
conf.usn,
|
||||||
|
&serde_json::to_vec(conf)?,
|
||||||
|
])?;
|
||||||
|
let id = self.db.last_insert_rowid();
|
||||||
|
if conf.id.0 != id {
|
||||||
|
conf.id.0 = id;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn upgrade_deck_conf_to_schema14(&self) -> Result<()> {
|
pub(super) fn upgrade_deck_conf_to_schema14(&self) -> Result<()> {
|
||||||
let conf = self
|
let conf = self
|
||||||
.db
|
.db
|
||||||
.query_row_and_then("select dconf from col", NO_PARAMS, |row| {
|
.query_row_and_then("select dconf from col", NO_PARAMS, |row| {
|
||||||
let conf: Result<HashMap<DeckConfID, DeckConf>> =
|
let conf: Result<HashMap<DeckConfID, DeckConfSchema11>> =
|
||||||
serde_json::from_str(row.get_raw(0).as_str()?).map_err(Into::into);
|
serde_json::from_str(row.get_raw(0).as_str()?).map_err(Into::into);
|
||||||
conf
|
conf
|
||||||
})?;
|
})?;
|
||||||
for (_, mut conf) in conf.into_iter() {
|
for (_, mut conf) in conf.into_iter() {
|
||||||
self.add_deck_conf(&mut conf)?;
|
self.add_deck_conf_schema14(&mut conf)?;
|
||||||
}
|
}
|
||||||
self.db.execute_batch("update col set dconf=''")?;
|
self.db.execute_batch("update col set dconf=''")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn downgrade_deck_conf_from_schema14(&self) -> Result<()> {
|
// schema 14->15
|
||||||
|
|
||||||
|
fn all_deck_config_schema14(&self) -> Result<Vec<DeckConfSchema11>> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached("select config from deck_config")?
|
||||||
|
.query_and_then(NO_PARAMS, |row| -> Result<_> {
|
||||||
|
Ok(serde_json::from_slice(row.get_raw(0).as_blob()?)?)
|
||||||
|
})?
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn upgrade_deck_conf_to_schema15(&self) -> Result<()> {
|
||||||
|
for conf in self.all_deck_config_schema14()? {
|
||||||
|
let conf: DeckConf = conf.into();
|
||||||
|
self.update_deck_conf(&conf)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema 15->11
|
||||||
|
|
||||||
|
pub(super) fn downgrade_deck_conf_from_schema15(&self) -> Result<()> {
|
||||||
let allconf = self.all_deck_config()?;
|
let allconf = self.all_deck_config()?;
|
||||||
let confmap: HashMap<DeckConfID, DeckConf> =
|
let confmap: HashMap<DeckConfID, DeckConfSchema11> = allconf
|
||||||
allconf.into_iter().map(|c| (c.id, c)).collect();
|
.into_iter()
|
||||||
|
.map(|c| -> DeckConfSchema11 { c.into() })
|
||||||
|
.map(|c| (c.id, c))
|
||||||
|
.collect();
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"update col set dconf=?",
|
"update col set dconf=?",
|
||||||
params![serde_json::to_string(&confmap)?],
|
params![serde_json::to_string(&confmap)?],
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ impl SqliteStorage {
|
||||||
.execute_batch(include_str!("schema15_upgrade.sql"))?;
|
.execute_batch(include_str!("schema15_upgrade.sql"))?;
|
||||||
self.upgrade_notetypes_to_schema15()?;
|
self.upgrade_notetypes_to_schema15()?;
|
||||||
self.upgrade_decks_to_schema15()?;
|
self.upgrade_decks_to_schema15()?;
|
||||||
|
self.upgrade_deck_conf_to_schema15()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -26,11 +27,11 @@ impl SqliteStorage {
|
||||||
pub(super) fn downgrade_to_schema_11(&self) -> Result<()> {
|
pub(super) fn downgrade_to_schema_11(&self) -> Result<()> {
|
||||||
self.begin_trx()?;
|
self.begin_trx()?;
|
||||||
|
|
||||||
|
self.downgrade_deck_conf_from_schema15()?;
|
||||||
self.downgrade_decks_from_schema15()?;
|
self.downgrade_decks_from_schema15()?;
|
||||||
self.downgrade_notetypes_from_schema15()?;
|
self.downgrade_notetypes_from_schema15()?;
|
||||||
self.downgrade_config_from_schema14()?;
|
self.downgrade_config_from_schema14()?;
|
||||||
self.downgrade_tags_from_schema14()?;
|
self.downgrade_tags_from_schema14()?;
|
||||||
self.downgrade_deck_conf_from_schema14()?;
|
|
||||||
self.db
|
self.db
|
||||||
.execute_batch(include_str!("schema11_downgrade.sql"))?;
|
.execute_batch(include_str!("schema11_downgrade.sql"))?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue