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
+157 -55

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
create table card_set
create table decks
(
id INTEGER
primary key autoincrement,
+4 -3
View File
@@ -2,10 +2,11 @@ create table history
(
id INTEGER
primary key autoincrement,
set_id INTEGER not null references card_set on delete cascade,
stat_id INTEGER not null references card_stats on delete cascade,
set_id INTEGER not null references decks on delete cascade,
stat_id INTEGER not null references states on delete cascade,
timestamp INTEGER not null,
answer 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
primary key autoincrement,
@@ -6,7 +6,7 @@ create table card_stats
references words
on delete cascade,
set_id INTEGER not null
references card_set
references decks
on delete cascade,
score INTEGER default 1 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
primary key autoincrement,
+2 -2
View File
@@ -7,7 +7,7 @@ create table words
tags TEXT not null,
more TEXT,
group_id integer default 1 not null
constraint words_word_group_id_fk
references word_group
constraint words_word_groups_id_fk
references word_groups
on update cascade on delete cascade
);
+37 -24
View File
@@ -1,13 +1,14 @@
use crate::app::AppState;
use crate::core::srs::OrderModule::{WorstWordsSRS, FSRS, RandomSRS, SemiRandomSRS};
use crate::core::srs::{
OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, WordOpenMode,
WorstWordsSRSModule,
FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
WordOpenMode, WorstWordsSRSModule,
};
use crate::data::entities::*;
use crate::data_provider::card_stats::{load_stats_of_deck, update_stat_score};
use crate::data_provider::history::push_note;
use crate::notification::{MaybeError, NotificationData};
use chrono::{Local, Utc};
use chrono::Utc;
use parking_lot::Mutex;
use std::sync::Arc;
@@ -16,9 +17,9 @@ pub struct DeckData {
pub(crate) words: Vec<WordData>,
pub(crate) set: Vec<CardStatistics>,
current_word_index: Option<usize>,
fsrs_indices: Vec<usize>,
state: Arc<Mutex<AppState>>,
order_module: OrderModule,
open_mode: OrderMode,
settings: DeckSettings,
}
impl DeckData {
@@ -42,44 +43,39 @@ impl DeckData {
.cloned()
.collect();
let today = Local::now().date_naive();
Ok(Self {
fsrs_indices: stats
.iter()
.enumerate()
.filter(|(_, c)| c.fsrs_open_day == today)
.map(|(i, _)| i)
.collect(),
set: stats,
words,
current_word_index: None,
state: state_for,
order_module: match settings.open_mode {
OrderMode::Default => OrderModule::SemiRandomSRS(SemiRandomSRSModule::new()),
OrderMode::TrainWorstFirst => {
OrderModule::WorstWordsSRS(WorstWordsSRSModule::new())
}
OrderMode::FullRandom => OrderModule::RandomSRS(RandomSRSModule::new()),
},
order_module: FSRS(FsrsModule::new()),
open_mode: settings.open_mode,
settings: settings.clone(),
})
}
pub fn next(&mut self) -> (WordData, CardStatistics) {
let index = match self.order_module {
OrderModule::SemiRandomSRS(ref mut module) => {
SemiRandomSRS(ref mut module) => {
if !module.initialized {
module.init(self.set.as_slice())
}
module.next(self.set.as_slice())
}
OrderModule::RandomSRS(ref mut module) => {
RandomSRS(ref mut module) => {
if !module.initialized {
module.init(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 {
module.init(self.set.as_slice())
}
@@ -98,15 +94,32 @@ impl DeckData {
let new_score = word.score;
match self.order_module {
OrderModule::SemiRandomSRS(ref mut module) => {
SemiRandomSRS(ref mut module) => {
module.open(status, index, word);
}
OrderModule::RandomSRS(ref mut module) => {
RandomSRS(ref mut module) => {
module.open(status, index, word);
}
OrderModule::WorstWordsSRS(ref mut module) => {
WorstWordsSRS(ref mut module) => {
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)?;
let _ = push_note(
+94 -6
View File
@@ -1,4 +1,6 @@
use crate::data::entities::CardStatistics;
use chrono::Local;
use fsrs::FSRS;
use rand::distr::weighted::WeightedIndex;
use rand::prelude::ThreadRng;
use rand::prelude::{Distribution, SliceRandom};
@@ -8,7 +10,7 @@ const MAX_HISTORY_LEN: usize = 20;
const MAX_HISTORY_LEN_PART: f32 = 0.33;
pub const MAX_SCORE: u8 = 25;
pub const FADE_PER_DAY: f32 = 0.95;
const PARTIAL_POOL_SIZE: usize = 10;
#[derive(Clone, PartialEq, Copy, Eq)]
pub enum OrderMode {
Default,
@@ -26,10 +28,11 @@ pub enum OrderModule {
SemiRandomSRS(SemiRandomSRSModule),
RandomSRS(RandomSRSModule),
WorstWordsSRS(WorstWordsSRSModule),
FSRS(FsrsModule),
}
pub trait SRSModule {
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]);
}
#[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]) {
self.initialized = true;
@@ -97,7 +100,7 @@ impl SRSModule for SemiRandomSRSModule {
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);
self.last_weights
.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]) {
self.initialized = true;
@@ -153,7 +156,7 @@ impl WorstWordsSRSModule {
pub(crate) fn new() -> WorstWordsSRSModule {
WorstWordsSRSModule {
initialized: false,
pool_size: 15,
pool_size: PARTIAL_POOL_SIZE,
rounds_count: 2,
rounds_remaining: 0,
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)]
pub enum WordOpenMode {
Easy,
+4 -4
View File
@@ -10,7 +10,7 @@ use rusqlite::Connection;
pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> {
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");
let iter = stmt
.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 {
let index : u32 = connection
.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.forward,
@@ -55,7 +55,7 @@ pub fn update_deck(set: &mut DeckSettings, connection: &Connection) -> MaybeErro
} else {
connection
.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.forward,
@@ -74,7 +74,7 @@ pub fn delete_set(set: &DeckSettings, connection: &Connection) -> MaybeError {
return Err(DbSaveError);
}
connection
.execute("DELETE FROM card_set WHERE id = ?1", (&set.id,))
.execute("DELETE FROM decks WHERE id = ?1", (&set.id,))
.map_err(|_| DbSaveError)?;
Ok(())
}
+4 -4
View File
@@ -13,7 +13,7 @@ pub fn load_stats_of_deck(
connection: &Connection,
) -> Result<Vec<CardStatistics>, NotificationData> {
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");
let iter = stmt
.query_map((set.id,), |row| {
@@ -59,7 +59,7 @@ pub fn add_stat_list(stats: &mut [CardStatistics], connection: &mut Connection)
{
let mut stmt = tx
.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");
@@ -98,7 +98,7 @@ pub fn update_stat_score(stat: &CardStatistics, connection: &Connection) -> Mayb
connection
.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),
)
.map_err(|_| DbSaveError)?;
@@ -117,7 +117,7 @@ pub fn delete_stats(stat: &[CardStatistics], connection: &Connection) -> MaybeEr
.join(", ");
connection
.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)?;
+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> {
let query = include_str!("../../sql/card_set.sql");
let query = include_str!("../../sql/decks.sql");
conn.execute(query, ())?;
Ok(())
}
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("insert into word_group (name) values (\"Слова\");", ())?;
conn.execute("insert into word_groups (name) values (\"Слова\");", ())?;
Ok(())
}
fn make_words(conn: &Connection) -> Result<(), rusqlite::Error> {
@@ -47,7 +47,7 @@ fn make_words(conn: &Connection) -> Result<(), rusqlite::Error> {
Ok(())
}
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, ())?;
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> {
let mut stmt = connection
.prepare("SELECT id, name FROM word_group")
.prepare("SELECT id, name FROM word_groups")
.expect("Failed to prepare statement");
let group_iter = stmt
.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 {
let index: u32 = connection
.query_row(
"INSERT INTO word_group (name) VALUES (?1) RETURNING id",
"INSERT INTO word_groups (name) VALUES (?1) RETURNING id",
(&group.name,),
|row| row.get(0),
)
@@ -154,7 +154,7 @@ pub fn update_group(group: &mut WordGroup, connection: &Connection) -> MaybeErro
} else {
connection
.execute(
"UPDATE word_group SET name = ?1 WHERE id = ?2",
"UPDATE word_groups SET name = ?1 WHERE id = ?2",
(&group.name, &group.id),
)
.map_err(|_| DbSaveError)?;
@@ -167,7 +167,7 @@ pub fn delete_group(group: &WordGroup, connection: &Connection) -> MaybeError {
return Err(DbSaveError);
}
connection
.execute("DELETE FROM word_group WHERE id = ?1", (&group.id,))
.execute("DELETE FROM word_groups WHERE id = ?1", (&group.id,))
.map_err(|_| DbSaveError)?;
Ok(())
}