rename tables

This commit is contained in:
micialware committed 2026-08-29 21:31:54 +03:00
1 parent 188a26e77d
commit a642a8fda4
11 files changed
+158 -56

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
create table card_set create table decks
( (
id INTEGER id INTEGER
primary key autoincrement, primary key autoincrement,
+4 -3
View File
@@ -2,10 +2,11 @@ create table history
( (
id INTEGER id INTEGER
primary key autoincrement, primary key autoincrement,
set_id INTEGER not null references card_set on delete cascade, set_id INTEGER not null references decks on delete cascade,
stat_id INTEGER not null references card_stats on delete cascade, stat_id INTEGER not null references states on delete cascade,
timestamp INTEGER not null, timestamp INTEGER not null,
answer INTEGER not null, answer INTEGER not null,
before INTEGER not null, before INTEGER not null,
after INTEGER not null after INTEGER not null,
fsrs INTEGER not null
); );
+2 -2
View File
@@ -1,4 +1,4 @@
create table card_stats create table states
( (
id INTEGER id INTEGER
primary key autoincrement, primary key autoincrement,
@@ -6,7 +6,7 @@ create table card_stats
references words references words
on delete cascade, on delete cascade,
set_id INTEGER not null set_id INTEGER not null
references card_set references decks
on delete cascade, on delete cascade,
score INTEGER default 1 not null, score INTEGER default 1 not null,
last_opened INTEGER not null, last_opened INTEGER not null,
+1 -1
View File
@@ -1,4 +1,4 @@
create table word_group create table word_groups
( (
id INTEGER id INTEGER
primary key autoincrement, primary key autoincrement,
+2 -2
View File
@@ -7,7 +7,7 @@ create table words
tags TEXT not null, tags TEXT not null,
more TEXT, more TEXT,
group_id integer default 1 not null group_id integer default 1 not null
constraint words_word_group_id_fk constraint words_word_groups_id_fk
references word_group references word_groups
on update cascade on delete cascade on update cascade on delete cascade
); );
+38 -25
View File
@@ -1,13 +1,14 @@
use crate::app::AppState; use crate::app::AppState;
use crate::core::srs::OrderModule::{WorstWordsSRS, FSRS, RandomSRS, SemiRandomSRS};
use crate::core::srs::{ use crate::core::srs::{
OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, WordOpenMode, FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
WorstWordsSRSModule, WordOpenMode, WorstWordsSRSModule,
}; };
use crate::data::entities::*; use crate::data::entities::*;
use crate::data_provider::card_stats::{load_stats_of_deck, update_stat_score}; use crate::data_provider::card_stats::{load_stats_of_deck, update_stat_score};
use crate::data_provider::history::push_note; use crate::data_provider::history::push_note;
use crate::notification::{MaybeError, NotificationData}; use crate::notification::{MaybeError, NotificationData};
use chrono::{Local, Utc}; use chrono::Utc;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
@@ -16,9 +17,9 @@ pub struct DeckData {
pub(crate) words: Vec<WordData>, pub(crate) words: Vec<WordData>,
pub(crate) set: Vec<CardStatistics>, pub(crate) set: Vec<CardStatistics>,
current_word_index: Option<usize>, current_word_index: Option<usize>,
fsrs_indices: Vec<usize>,
state: Arc<Mutex<AppState>>, state: Arc<Mutex<AppState>>,
order_module: OrderModule, order_module: OrderModule,
open_mode: OrderMode,
settings: DeckSettings, settings: DeckSettings,
} }
impl DeckData { impl DeckData {
@@ -41,45 +42,40 @@ impl DeckData {
.map(|index| &state_locked.dictionary[index]) .map(|index| &state_locked.dictionary[index])
.cloned() .cloned()
.collect(); .collect();
let today = Local::now().date_naive();
Ok(Self { Ok(Self {
fsrs_indices: stats
.iter()
.enumerate()
.filter(|(_, c)| c.fsrs_open_day == today)
.map(|(i, _)| i)
.collect(),
set: stats, set: stats,
words, words,
current_word_index: None, current_word_index: None,
state: state_for, state: state_for,
order_module: match settings.open_mode { order_module: FSRS(FsrsModule::new()),
OrderMode::Default => OrderModule::SemiRandomSRS(SemiRandomSRSModule::new()), open_mode: settings.open_mode,
OrderMode::TrainWorstFirst => {
OrderModule::WorstWordsSRS(WorstWordsSRSModule::new())
}
OrderMode::FullRandom => OrderModule::RandomSRS(RandomSRSModule::new()),
},
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 { let index = match self.order_module {
OrderModule::SemiRandomSRS(ref mut module) => { SemiRandomSRS(ref mut module) => {
if !module.initialized { if !module.initialized {
module.init(self.set.as_slice()) module.init(self.set.as_slice())
} }
module.next(self.set.as_slice()) module.next(self.set.as_slice())
} }
OrderModule::RandomSRS(ref mut module) => {
RandomSRS(ref mut module) => {
if !module.initialized { if !module.initialized {
module.init(self.set.as_slice()) module.init(self.set.as_slice())
} }
module.next(self.set.as_slice()) module.next(self.set.as_slice())
} }
OrderModule::WorstWordsSRS(ref mut module) => { WorstWordsSRS(ref mut module) => {
if !module.initialized {
module.init(self.set.as_slice())
}
module.next(self.set.as_slice())
}
FSRS(ref mut module) => {
if !module.initialized { if !module.initialized {
module.init(self.set.as_slice()) module.init(self.set.as_slice())
} }
@@ -98,15 +94,32 @@ impl DeckData {
let new_score = word.score; let new_score = word.score;
match self.order_module { match self.order_module {
OrderModule::SemiRandomSRS(ref mut module) => { SemiRandomSRS(ref mut module) => {
module.open(status, index, word); module.open(status, index, word);
} }
OrderModule::RandomSRS(ref mut module) => { RandomSRS(ref mut module) => {
module.open(status, index, word); module.open(status, index, word);
} }
OrderModule::WorstWordsSRS(ref mut module) => { WorstWordsSRS(ref mut module) => {
module.open(status, index, word); module.open(status, index, word);
} }
FSRS(ref mut module) => {
module.open(status, index, word);
}
}
if let FSRS(ref fsrs) = self.order_module {
if fsrs.finished() {
self.order_module = match self.open_mode {
OrderMode::Default => {
SemiRandomSRS(SemiRandomSRSModule::new())
}
OrderMode::TrainWorstFirst => {
WorstWordsSRS(WorstWordsSRSModule::new())
}
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()),
}
}
} }
update_stat_score(word, &self.state.lock().connection)?; update_stat_score(word, &self.state.lock().connection)?;
let _ = push_note( let _ = push_note(
+94 -6
View File
@@ -1,4 +1,6 @@
use crate::data::entities::CardStatistics; use crate::data::entities::CardStatistics;
use chrono::Local;
use fsrs::FSRS;
use rand::distr::weighted::WeightedIndex; use rand::distr::weighted::WeightedIndex;
use rand::prelude::ThreadRng; use rand::prelude::ThreadRng;
use rand::prelude::{Distribution, SliceRandom}; use rand::prelude::{Distribution, SliceRandom};
@@ -8,7 +10,7 @@ const MAX_HISTORY_LEN: usize = 20;
const MAX_HISTORY_LEN_PART: f32 = 0.33; const MAX_HISTORY_LEN_PART: f32 = 0.33;
pub const MAX_SCORE: u8 = 25; pub const MAX_SCORE: u8 = 25;
pub const FADE_PER_DAY: f32 = 0.95; pub const FADE_PER_DAY: f32 = 0.95;
const PARTIAL_POOL_SIZE: usize = 10;
#[derive(Clone, PartialEq, Copy, Eq)] #[derive(Clone, PartialEq, Copy, Eq)]
pub enum OrderMode { pub enum OrderMode {
Default, Default,
@@ -26,10 +28,11 @@ pub enum OrderModule {
SemiRandomSRS(SemiRandomSRSModule), SemiRandomSRS(SemiRandomSRSModule),
RandomSRS(RandomSRSModule), RandomSRS(RandomSRSModule),
WorstWordsSRS(WorstWordsSRSModule), WorstWordsSRS(WorstWordsSRSModule),
FSRS(FsrsModule),
} }
pub trait SRSModule { pub trait SRSModule {
fn next(&mut self, set: &[CardStatistics]) -> usize; fn next(&mut self, set: &[CardStatistics]) -> usize;
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &CardStatistics); fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardStatistics);
fn init(&mut self, set: &[CardStatistics]); fn init(&mut self, set: &[CardStatistics]);
} }
#[derive(Clone)] #[derive(Clone)]
@@ -57,7 +60,7 @@ impl SRSModule for RandomSRSModule {
} }
} }
fn open(&mut self, _: WordOpenMode, _: usize, _: &CardStatistics) {} fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardStatistics) {}
fn init(&mut self, set: &[CardStatistics]) { fn init(&mut self, set: &[CardStatistics]) {
self.initialized = true; self.initialized = true;
@@ -97,7 +100,7 @@ impl SRSModule for SemiRandomSRSModule {
index index
} }
fn open(&mut self, _: WordOpenMode, index: usize, word: &CardStatistics) { fn open(&mut self, _: WordOpenMode, index: usize, word: &mut 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)])
@@ -143,7 +146,7 @@ impl SRSModule for WorstWordsSRSModule {
} }
} }
fn open(&mut self, _: WordOpenMode, _: usize, _: &CardStatistics) {} fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardStatistics) {}
fn init(&mut self, _: &[CardStatistics]) { fn init(&mut self, _: &[CardStatistics]) {
self.initialized = true; self.initialized = true;
@@ -153,7 +156,7 @@ impl WorstWordsSRSModule {
pub(crate) fn new() -> WorstWordsSRSModule { pub(crate) fn new() -> WorstWordsSRSModule {
WorstWordsSRSModule { WorstWordsSRSModule {
initialized: false, initialized: false,
pool_size: 15, pool_size: PARTIAL_POOL_SIZE,
rounds_count: 2, rounds_count: 2,
rounds_remaining: 0, rounds_remaining: 0,
pool: vec![], pool: vec![],
@@ -172,6 +175,91 @@ impl WorstWordsSRSModule {
} }
} }
#[derive(Clone)]
pub struct FsrsModule {
today_set: Vec<usize>,
solved: Vec<usize>,
current_group: Vec<usize>,
queue: Vec<usize>,
rng: ThreadRng,
fsrs: FSRS,
pub(crate) initialized: bool
}
impl SRSModule for FsrsModule {
fn next(&mut self, set: &[CardStatistics]) -> usize {
if self.solved.len() == self.current_group.len() {
self.fill_partial_pool();
}
if self.queue.is_empty() {
self.fill_queue();
self.queue.shuffle(&mut self.rng);
}
self.queue.pop().expect("empty queue")
}
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardStatistics) {
let today = Local::now().date_naive();
let delta = today - updated_word.fsrs_opened;
let delta_days = delta.num_days();
if updated_word.fsrs_open_day != today{
self.solved.push(index);
}
}
fn init(&mut self, set: &[CardStatistics]) {
self.rng = rng();
let today = Local::now().date_naive();
self.today_set = set
.iter()
.enumerate()
.filter(|(_, c)| c.fsrs_open_day <= today)
.map(|(index, _)| index)
.collect();
self.today_set.shuffle(&mut self.rng);
self.fill_partial_pool();
self.initialized = true;
}
}
impl FsrsModule {
pub fn new() -> Self{
Self{
today_set: vec![],
current_group: vec![],
queue: vec![],
solved: vec![],
rng: Default::default(),
fsrs: FSRS::default(),
initialized: false,
}
}
fn fill_partial_pool(&mut self) {
self.current_group.clear();
self.current_group
.extend(self.today_set.iter().take(PARTIAL_POOL_SIZE));
self.current_group.iter().for_each(|_| {
self.today_set.remove(0);
});
self.queue.clear();
self.queue
.extend(self.current_group.clone());
self.solved.clear();
}
fn fill_queue(&mut self) {
self.queue.clear();
self.queue.extend(self.current_group.iter().filter(|c| !self.today_set.contains(*c)));
}
pub fn finished(&self) -> bool {
self.today_set.len() == self.solved.len()
}
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum WordOpenMode { pub enum WordOpenMode {
Easy, Easy,
+4 -4
View File
@@ -10,7 +10,7 @@ use rusqlite::Connection;
pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> { pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> {
let mut stmt = connection let mut stmt = connection
.prepare("SELECT id, name, forward, backward, filter FROM card_set") .prepare("SELECT id, name, forward, backward, filter FROM decks")
.expect("Failed to prepare state query"); .expect("Failed to prepare state query");
let iter = stmt let iter = stmt
.query_map([], |row| { .query_map([], |row| {
@@ -34,7 +34,7 @@ pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, Notificat
pub fn add_set(set: &mut DeckSettings, connection: &Connection) -> MaybeError { pub fn add_set(set: &mut DeckSettings, connection: &Connection) -> MaybeError {
let index : u32 = connection let index : u32 = connection
.query_row( .query_row(
"INSERT INTO card_set (name, forward, backward, filter) VALUES (?1, ?2, ?3, ?4) RETURNING id", "INSERT INTO decks (name, forward, backward, filter) VALUES (?1, ?2, ?3, ?4) RETURNING id",
( (
&set.name, &set.name,
&set.forward, &set.forward,
@@ -55,7 +55,7 @@ pub fn update_deck(set: &mut DeckSettings, connection: &Connection) -> MaybeErro
} else { } else {
connection connection
.execute( .execute(
"UPDATE card_set SET name = ?1, forward = ?2, backward = ?3, filter = ?4 WHERE id = ?5", "UPDATE decks SET name = ?1, forward = ?2, backward = ?3, filter = ?4 WHERE id = ?5",
( (
&set.name, &set.name,
&set.forward, &set.forward,
@@ -74,7 +74,7 @@ pub fn delete_set(set: &DeckSettings, connection: &Connection) -> MaybeError {
return Err(DbSaveError); return Err(DbSaveError);
} }
connection connection
.execute("DELETE FROM card_set WHERE id = ?1", (&set.id,)) .execute("DELETE FROM decks WHERE id = ?1", (&set.id,))
.map_err(|_| DbSaveError)?; .map_err(|_| DbSaveError)?;
Ok(()) Ok(())
} }
+4 -4
View File
@@ -13,7 +13,7 @@ pub fn load_stats_of_deck(
connection: &Connection, connection: &Connection,
) -> Result<Vec<CardStatistics>, NotificationData> { ) -> Result<Vec<CardStatistics>, NotificationData> {
let mut stmt = connection let mut stmt = connection
.prepare("SELECT id, word_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM card_stats WHERE set_id = ?1") .prepare("SELECT id, word_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states WHERE set_id = ?1")
.expect("Failed to prepare state query"); .expect("Failed to prepare state query");
let iter = stmt let iter = stmt
.query_map((set.id,), |row| { .query_map((set.id,), |row| {
@@ -59,7 +59,7 @@ pub fn add_stat_list(stats: &mut [CardStatistics], connection: &mut Connection)
{ {
let mut stmt = tx let mut stmt = tx
.prepare( .prepare(
"INSERT INTO card_stats (word_id, set_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4)", "INSERT INTO states (word_id, set_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4)",
) )
.expect("Failed to prepare state query"); .expect("Failed to prepare state query");
@@ -98,7 +98,7 @@ pub fn update_stat_score(stat: &CardStatistics, connection: &Connection) -> Mayb
connection connection
.execute( .execute(
"UPDATE card_stats SET score = ?1, last_opened = ?2 WHERE id = ?3", "UPDATE states SET score = ?1, last_opened = ?2 WHERE id = ?3",
(&stat.score, &stat.last_open.timestamp(), &stat.id), (&stat.score, &stat.last_open.timestamp(), &stat.id),
) )
.map_err(|_| DbSaveError)?; .map_err(|_| DbSaveError)?;
@@ -117,7 +117,7 @@ pub fn delete_stats(stat: &[CardStatistics], connection: &Connection) -> MaybeEr
.join(", "); .join(", ");
connection connection
.execute( .execute(
format!("DELETE FROM card_stats WHERE id in ({ids})").as_str(), format!("DELETE FROM states WHERE id in ({ids})").as_str(),
(), (),
) )
.map_err(|_| DbSaveError)?; .map_err(|_| DbSaveError)?;
+4 -4
View File
@@ -30,15 +30,15 @@ fn make_settings(conn: &Connection) -> Result<(), rusqlite::Error> {
} }
fn make_card_set(conn: &Connection) -> Result<(), rusqlite::Error> { fn make_card_set(conn: &Connection) -> Result<(), rusqlite::Error> {
let query = include_str!("../../sql/card_set.sql"); let query = include_str!("../../sql/decks.sql");
conn.execute(query, ())?; conn.execute(query, ())?;
Ok(()) Ok(())
} }
fn make_word_group(conn: &Connection) -> Result<(), rusqlite::Error> { fn make_word_group(conn: &Connection) -> Result<(), rusqlite::Error> {
let query = include_str!("../../sql/word_group.sql"); let query = include_str!("../../sql/word_groups.sql");
conn.execute(query, ())?; conn.execute(query, ())?;
conn.execute("insert into word_group (name) values (\"Слова\");", ())?; conn.execute("insert into word_groups (name) values (\"Слова\");", ())?;
Ok(()) Ok(())
} }
fn make_words(conn: &Connection) -> Result<(), rusqlite::Error> { fn make_words(conn: &Connection) -> Result<(), rusqlite::Error> {
@@ -47,7 +47,7 @@ fn make_words(conn: &Connection) -> Result<(), rusqlite::Error> {
Ok(()) Ok(())
} }
fn make_card_stats(conn: &Connection) -> Result<(), rusqlite::Error> { fn make_card_stats(conn: &Connection) -> Result<(), rusqlite::Error> {
let query = include_str!("../../sql/card_stats.sql"); let query = include_str!("../../sql/states.sql");
conn.execute(query, ())?; conn.execute(query, ())?;
Ok(()) Ok(())
} }
+4 -4
View File
@@ -116,7 +116,7 @@ pub fn load_words(connection: &Connection) -> Result<Vec<WordData>, Notification
pub fn load_word_groups(connection: &Connection) -> Result<Vec<WordGroup>, NotificationData> { pub fn load_word_groups(connection: &Connection) -> Result<Vec<WordGroup>, NotificationData> {
let mut stmt = connection let mut stmt = connection
.prepare("SELECT id, name FROM word_group") .prepare("SELECT id, name FROM word_groups")
.expect("Failed to prepare statement"); .expect("Failed to prepare statement");
let group_iter = stmt let group_iter = stmt
.query_map([], |row| { .query_map([], |row| {
@@ -138,7 +138,7 @@ pub fn load_word_groups(connection: &Connection) -> Result<Vec<WordGroup>, Notif
pub fn add_group(group: &mut WordGroup, connection: &Connection) -> MaybeError { pub fn add_group(group: &mut WordGroup, connection: &Connection) -> MaybeError {
let index: u32 = connection let index: u32 = connection
.query_row( .query_row(
"INSERT INTO word_group (name) VALUES (?1) RETURNING id", "INSERT INTO word_groups (name) VALUES (?1) RETURNING id",
(&group.name,), (&group.name,),
|row| row.get(0), |row| row.get(0),
) )
@@ -154,7 +154,7 @@ pub fn update_group(group: &mut WordGroup, connection: &Connection) -> MaybeErro
} else { } else {
connection connection
.execute( .execute(
"UPDATE word_group SET name = ?1 WHERE id = ?2", "UPDATE word_groups SET name = ?1 WHERE id = ?2",
(&group.name, &group.id), (&group.name, &group.id),
) )
.map_err(|_| DbSaveError)?; .map_err(|_| DbSaveError)?;
@@ -167,7 +167,7 @@ pub fn delete_group(group: &WordGroup, connection: &Connection) -> MaybeError {
return Err(DbSaveError); return Err(DbSaveError);
} }
connection connection
.execute("DELETE FROM word_group WHERE id = ?1", (&group.id,)) .execute("DELETE FROM word_groups WHERE id = ?1", (&group.id,))
.map_err(|_| DbSaveError)?; .map_err(|_| DbSaveError)?;
Ok(()) Ok(())
} }