More migration code

This commit is contained in:
2026-08-18 12:58:02 +03:00
parent 97c76350e1
commit 9e28b05167
4 changed files with 61 additions and 97 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ fn parse_history_items(strings: Vec<String>) -> Vec<HistoryItem> {
if let [time, word, mode, before, after] = string.split(';').collect::<Vec<&str>>()[..] { if let [time, word, mode, before, after] = string.split(';').collect::<Vec<&str>>()[..] {
items.push(HistoryItem { items.push(HistoryItem {
timestamp: DateTime::from_timestamp(time.parse().unwrap(), 0).unwrap(), timestamp: DateTime::from_timestamp(time.parse().unwrap(), 0).unwrap(),
word_id: word.parse::<u32>().unwrap().into(), word_id: word.parse::<Id>().unwrap().into(),
mode: match mode.parse::<u8>().unwrap() { mode: match mode.parse::<u8>().unwrap() {
2 => WordOpenMode::Hard, 2 => WordOpenMode::Hard,
3 => WordOpenMode::Ok, 3 => WordOpenMode::Ok,
+48 -79
View File
@@ -1,9 +1,9 @@
use crate::data_provider::card_stats::{delete_stat, load_stats_of_set, update_stat_score};
use crate::data_provider::history::{push_note, HistoryItem};
use crate::AppState; use crate::AppState;
use crate::data_provider::card_stats::{delete_stat, load_stats_of_set, update_stat_score};
use crate::data_provider::history::{HistoryItem, push_note};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use rand::distr::weighted::WeightedIndex;
use rand::distr::Distribution; use rand::distr::Distribution;
use rand::distr::weighted::WeightedIndex;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use rand::rng; use rand::rng;
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
@@ -11,11 +11,13 @@ use rayon::iter::IndexedParallelIterator;
use rayon::iter::IntoParallelRefIterator; use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use rhai::{Engine, Scope}; use rhai::{Engine, Scope};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
use rusqlite::ToSql; use rusqlite::ToSql;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::num::ParseIntError;
use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
@@ -26,6 +28,47 @@ const FADE_PER_DAY: f32 = 0.95;
#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy)] #[derive(Clone, PartialEq, Eq, Debug, Hash, Copy)]
pub(crate) struct Id(u32); pub(crate) struct Id(u32);
pub const INVALID_ID: Id = Id(0); pub const INVALID_ID: Id = Id(0);
impl FromStr for Id {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Id(s.parse::<u32>()?))
}
}
impl Into<Id> for u32 {
fn into(self) -> Id {
Id(self)
}
}
impl Into<u32> for Id {
fn into(self) -> u32 {
self.0
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromSql for Id {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(Id(i as u32)),
_ => Err(FromSqlError::InvalidType),
}
}
}
impl ToSql for Id {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0 as i64))
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct KanaSet { pub struct KanaSet {
name: String, name: String,
@@ -33,13 +76,11 @@ pub struct KanaSet {
pub(crate) dictionary: Vec<Vec<(String, String)>>, pub(crate) dictionary: Vec<Vec<(String, String)>>,
pub(crate) include_map: [bool; 10], pub(crate) include_map: [bool; 10],
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum KanaType { pub enum KanaType {
Hiragana, Hiragana,
Katakana, Katakana,
} }
impl KanaSet { impl KanaSet {
pub fn hiragana() -> Self { pub fn hiragana() -> Self {
Self { Self {
@@ -219,7 +260,6 @@ impl KanaSet {
current_set current_set
} }
} }
impl Default for KanaSet { impl Default for KanaSet {
fn default() -> Self { fn default() -> Self {
KanaSet::hiragana() KanaSet::hiragana()
@@ -230,7 +270,6 @@ impl PartialEq<Self> for KanaSet {
self.name == other.name self.name == other.name
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct WordData { pub struct WordData {
pub id: Id, pub id: Id,
@@ -240,11 +279,10 @@ pub struct WordData {
pub additional: HashMap<String, String>, pub additional: HashMap<String, String>,
pub group_id: Id, pub group_id: Id,
} }
impl WordData { impl WordData {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
id: 0.into(), id: INVALID_ID,
key: String::new(), key: String::new(),
value: String::new(), value: String::new(),
tags: String::new(), tags: String::new(),
@@ -253,13 +291,11 @@ impl WordData {
} }
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct WordGroup { pub struct WordGroup {
pub id: Id, pub id: Id,
pub name: String, pub name: String,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct CardStatistics { pub struct CardStatistics {
pub id: Id, pub id: Id,
@@ -268,7 +304,6 @@ pub struct CardStatistics {
pub last_open: DateTime<Utc>, pub last_open: DateTime<Utc>,
pub score: u8, pub score: u8,
} }
impl CardStatistics { impl CardStatistics {
pub fn update(&mut self, status: WordOpenMode) { pub fn update(&mut self, status: WordOpenMode) {
match status { match status {
@@ -295,7 +330,6 @@ impl CardStatistics {
(self.score as f32 * multiplier).max(1.0) (self.score as f32 * multiplier).max(1.0)
} }
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum WordOpenMode { pub enum WordOpenMode {
Easy, Easy,
@@ -303,7 +337,6 @@ pub enum WordOpenMode {
Hard, Hard,
None, None,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct DeckData { pub struct DeckData {
words: Vec<WordData>, words: Vec<WordData>,
@@ -313,7 +346,6 @@ pub struct DeckData {
order_module: OrderModule, order_module: OrderModule,
settings: DeckSettings, settings: DeckSettings,
} }
impl DeckData { impl DeckData {
pub fn new(settings: &DeckSettings, state: Arc<Mutex<AppState>>) -> Self { pub fn new(settings: &DeckSettings, state: Arc<Mutex<AppState>>) -> Self {
let state_for = state.clone(); let state_for = state.clone();
@@ -353,7 +385,6 @@ impl DeckData {
settings: settings.clone(), settings: settings.clone(),
} }
} }
pub fn next(&mut self) -> (WordData, CardStatistics) { pub fn next(&mut self) -> (WordData, CardStatistics) {
let index = match self.order_module.clone() { let index = match self.order_module.clone() {
OrderModule::SemiRandomSRS(mut module) => { OrderModule::SemiRandomSRS(mut module) => {
@@ -385,7 +416,6 @@ impl DeckData {
self.current_word_index = Some(index); self.current_word_index = Some(index);
(self.words[index].clone(), self.set[index].clone()) (self.words[index].clone(), self.set[index].clone())
} }
pub fn open(&mut self, status: WordOpenMode) { pub fn open(&mut self, status: WordOpenMode) {
if self.current_word_index.is_none() { if self.current_word_index.is_none() {
return; return;
@@ -423,45 +453,38 @@ impl DeckData {
}, },
) )
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.set.len() self.set.len()
} }
} }
#[derive(Clone, PartialEq, Copy, Eq)] #[derive(Clone, PartialEq, Copy, Eq)]
pub enum OrderMode { pub enum OrderMode {
Default, Default,
TrainWorstFirst, TrainWorstFirst,
FullRandom, FullRandom,
} }
#[derive(Clone, Copy, Eq, PartialEq, Default)] #[derive(Clone, Copy, Eq, PartialEq, Default)]
pub enum AppendMode { pub enum AppendMode {
Full, Full,
#[default] #[default]
Manual, Manual,
} }
#[derive(Clone)] #[derive(Clone)]
enum OrderModule { enum OrderModule {
SemiRandomSRS(SemiRandomSRSModule), SemiRandomSRS(SemiRandomSRSModule),
RandomSRS(RandomSRSModule), RandomSRS(RandomSRSModule),
WorstWordsSRS(WorstWordsSRSModule), WorstWordsSRS(WorstWordsSRSModule),
} }
trait SRSModule { trait SRSModule {
fn next(&mut self, set: &mut DeckData) -> usize; fn next(&mut self, set: &mut DeckData) -> usize;
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: CardStatistics); fn open(&mut self, status: WordOpenMode, index: usize, updated_word: CardStatistics);
fn init(&mut self, set: &mut DeckData); fn init(&mut self, set: &mut DeckData);
} }
#[derive(Clone)] #[derive(Clone)]
struct RandomSRSModule { struct RandomSRSModule {
basket: Vec<usize>, basket: Vec<usize>,
initialized: bool, initialized: bool,
} }
impl RandomSRSModule { impl RandomSRSModule {
fn new() -> RandomSRSModule { fn new() -> RandomSRSModule {
Self { Self {
@@ -470,7 +493,6 @@ impl RandomSRSModule {
} }
} }
} }
impl SRSModule for RandomSRSModule { impl SRSModule for RandomSRSModule {
fn next(&mut self, set: &mut DeckData) -> usize { fn next(&mut self, set: &mut DeckData) -> usize {
if self.basket.is_empty() { if self.basket.is_empty() {
@@ -489,7 +511,6 @@ impl SRSModule for RandomSRSModule {
self.basket.shuffle(&mut rand::rng()) self.basket.shuffle(&mut rand::rng())
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct SemiRandomSRSModule { struct SemiRandomSRSModule {
history: Vec<usize>, history: Vec<usize>,
@@ -497,7 +518,6 @@ struct SemiRandomSRSModule {
generator: ThreadRng, generator: ThreadRng,
initialized: bool, initialized: bool,
} }
impl SemiRandomSRSModule { impl SemiRandomSRSModule {
fn new() -> SemiRandomSRSModule { fn new() -> SemiRandomSRSModule {
SemiRandomSRSModule { SemiRandomSRSModule {
@@ -508,7 +528,6 @@ impl SemiRandomSRSModule {
} }
} }
} }
impl SRSModule for SemiRandomSRSModule { impl SRSModule for SemiRandomSRSModule {
fn next(&mut self, set: &mut DeckData) -> usize { fn next(&mut self, set: &mut DeckData) -> usize {
let index = self.last_weights.sample(&mut self.generator); let index = self.last_weights.sample(&mut self.generator);
@@ -524,14 +543,12 @@ impl SRSModule for SemiRandomSRSModule {
index index
} }
fn open(&mut self, _: WordOpenMode, index: usize, word: CardStatistics) { fn open(&mut self, _: WordOpenMode, index: usize, word: CardStatistics) {
let new_weight = (100.0 / word.calculated_score()).powf(2.0); let new_weight = (100.0 / word.calculated_score()).powf(2.0);
self.last_weights self.last_weights
.update_weights(&[(index, &new_weight)]) .update_weights(&[(index, &new_weight)])
.unwrap(); .unwrap();
} }
fn init(&mut self, set: &mut DeckData) { fn init(&mut self, set: &mut DeckData) {
self.initialized = true; self.initialized = true;
let weights = set let weights = set
@@ -542,13 +559,11 @@ impl SRSModule for SemiRandomSRSModule {
self.last_weights = WeightedIndex::new(weights).unwrap(); self.last_weights = WeightedIndex::new(weights).unwrap();
} }
} }
impl SemiRandomSRSModule { impl SemiRandomSRSModule {
fn history_len(&self, set: &DeckData) -> usize { fn history_len(&self, set: &DeckData) -> usize {
((set.len() as f32 * MAX_HISTORY_LEN_PART) as usize).clamp(1, MAX_HISTORY_LEN) ((set.len() as f32 * MAX_HISTORY_LEN_PART) as usize).clamp(1, MAX_HISTORY_LEN)
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct WorstWordsSRSModule { struct WorstWordsSRSModule {
initialized: bool, initialized: bool,
@@ -558,7 +573,6 @@ struct WorstWordsSRSModule {
queue: Vec<usize>, queue: Vec<usize>,
rounds_count: u8, rounds_count: u8,
} }
impl SRSModule for WorstWordsSRSModule { impl SRSModule for WorstWordsSRSModule {
fn next(&mut self, set: &mut DeckData) -> usize { fn next(&mut self, set: &mut DeckData) -> usize {
if self.rounds_remaining == 0 { if self.rounds_remaining == 0 {
@@ -580,7 +594,6 @@ impl SRSModule for WorstWordsSRSModule {
self.initialized = true; self.initialized = true;
} }
} }
impl WorstWordsSRSModule { impl WorstWordsSRSModule {
fn new() -> WorstWordsSRSModule { fn new() -> WorstWordsSRSModule {
WorstWordsSRSModule { WorstWordsSRSModule {
@@ -592,7 +605,6 @@ impl WorstWordsSRSModule {
queue: vec![], queue: vec![],
} }
} }
fn fill_pool(&mut self, set: &DeckData) { fn fill_pool(&mut self, set: &DeckData) {
let mut sorted = set let mut sorted = set
.set .set
@@ -609,7 +621,6 @@ impl WorstWordsSRSModule {
self.pool = worst; self.pool = worst;
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct DeckSettings { pub struct DeckSettings {
pub id: Id, pub id: Id,
@@ -621,7 +632,6 @@ pub struct DeckSettings {
pub worst_words_list: Option<Vec<WordData>>, pub worst_words_list: Option<Vec<WordData>>,
pub open_mode: OrderMode, pub open_mode: OrderMode,
} }
impl DeckSettings { impl DeckSettings {
pub(crate) fn with_name(name: String) -> DeckSettings { pub(crate) fn with_name(name: String) -> DeckSettings {
DeckSettings { DeckSettings {
@@ -635,13 +645,11 @@ impl DeckSettings {
open_mode: OrderMode::Default, open_mode: OrderMode::Default,
} }
} }
pub(crate) fn check_filter(&self) -> bool { pub(crate) fn check_filter(&self) -> bool {
let engine = Engine::new(); let engine = Engine::new();
let ast = engine.compile(&self.filter); let ast = engine.compile(&self.filter);
ast.is_ok() ast.is_ok()
} }
pub fn get_word_list(&self, state: &AppState) -> Vec<usize> { pub fn get_word_list(&self, state: &AppState) -> Vec<usize> {
let time = Instant::now(); let time = Instant::now();
let mut list = vec![]; let mut list = vec![];
@@ -692,46 +700,7 @@ impl DeckSettings {
list list
} }
pub fn require_speech(&self) -> bool { pub fn require_speech(&self) -> bool {
self.forward == "speech" || self.backward == "speech" self.forward == "speech" || self.backward == "speech"
} }
}
impl Into<Id> for u32{
fn into(self) -> Id {
Id(self)
}
}
// impl From<u32> for Id{
// fn from(id: u32) -> Id{
// Id(id)
// }
// }
impl Into<u32> for Id{
fn into(self) -> u32 {
self.0
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromSql for Id{
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(Id(i as u32)),
_ => Err(FromSqlError::InvalidType),
}
}
}
impl ToSql for Id{
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0 as i64))
}
} }
+2 -2
View File
@@ -128,8 +128,8 @@ impl ScreenState {
let mut vec = vec![]; let mut vec = vec![];
let history_file_name = file.file_name().into_string().unwrap(); let history_file_name = file.file_name().into_string().unwrap();
let id : Id = history_file_name[4..history_file_name.len() - 12] let id : Id = history_file_name[4..history_file_name.len() - 12]
.parse::<u32>() .parse::<Id>()
.unwrap().into(); .unwrap();
let history = get_history_of_set(id); let history = get_history_of_set(id);
let by_date = history.chunk_by(|x, x1| { let by_date = history.chunk_by(|x, x1| {
+10 -15
View File
@@ -27,11 +27,9 @@ use std::sync::{Arc, Mutex};
#[derive(Clone)] #[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
correct_filters: Vec<bool>,
current_sets_cards_cache: HashMap<usize, (Vec<usize>, Vec<usize>)>,
word_id_index_map: HashMap<u32, usize>, word_id_index_map: HashMap<u32, usize>,
local_settings: HashMap<u32, DeckLocalSetting>, view_data: Option<DeckViewData>,
view_data: Option<SetViewData>, decks: Vec<DeckSettings>,
state: Arc<Mutex<AppState>>, state: Arc<Mutex<AppState>>,
} }
@@ -489,20 +487,17 @@ pub enum RepetitionsMessage {
AppendWords(usize), AppendWords(usize),
} }
#[derive(Clone, Default)] #[derive(Clone)]
struct DeckLocalSetting { struct DeckViewData {
set_settings: DeckSettings,
existing_words_indices: Option<Vec<usize>>,
available_words_indices: Option<Vec<usize>>,
append_mode: AppendMode, append_mode: AppendMode,
append_warning: bool, append_warning: bool,
valid_filter: bool,
index: usize,
} }
impl Deref for DeckViewData {
#[derive(Clone)]
struct SetViewData{
set_local_settings: DeckLocalSetting,
set_settings: DeckSettings,
existing_words_indices: Vec<usize>,
available_words_indices: Vec<usize>,
}
impl Deref for SetViewData {
type Target = DeckSettings; type Target = DeckSettings;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {