2026-09-09 10:08:52 +03:00
|
|
|
use std::sync::Arc;
|
2026-08-30 20:43:31 +03:00
|
|
|
use crate::data::entities::{CardState, DeckSettings};
|
2026-09-09 10:08:52 +03:00
|
|
|
use crate::data::repositories::{DataRepository, DataRepositoryInner, Query};
|
2026-08-31 11:52:54 +03:00
|
|
|
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
2026-08-24 18:16:41 +03:00
|
|
|
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
2026-08-25 12:04:07 +03:00
|
|
|
use crate::notification::{MaybeError, NotificationData};
|
2026-08-28 18:52:38 +03:00
|
|
|
use chrono::NaiveDate;
|
|
|
|
|
use fsrs::MemoryState;
|
2026-08-24 18:16:41 +03:00
|
|
|
use log::info;
|
2026-08-30 20:43:31 +03:00
|
|
|
use rusqlite::{Connection, Error, Params, Row, params};
|
2026-07-29 13:30:49 +03:00
|
|
|
use std::time::Instant;
|
2026-09-09 10:08:52 +03:00
|
|
|
use crate::data::Id;
|
2026-04-13 22:25:58 +03:00
|
|
|
|
2026-09-08 00:09:13 +03:00
|
|
|
pub type CardStateRepository =
|
2026-09-09 10:08:52 +03:00
|
|
|
DataRepository<CardState, CardStateDbLayer>;
|
2026-09-08 00:09:13 +03:00
|
|
|
pub type CardStateRepositoryInner =
|
2026-09-09 10:08:52 +03:00
|
|
|
DataRepositoryInner<CardState, CardStateDbLayer>;
|
|
|
|
|
pub type CardStateDbLayer = SqliteDbLayer<CardState, CardStatesQuerySet>;
|
2026-08-31 14:35:11 +03:00
|
|
|
#[derive(Clone)]
|
2026-08-30 20:43:31 +03:00
|
|
|
pub struct CardStatesQuerySet;
|
|
|
|
|
impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
|
|
|
|
|
fn load() -> &'static str {
|
2026-09-06 23:55:50 +03:00
|
|
|
"SELECT id, word_id, deck_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states"
|
2026-08-30 20:43:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn save() -> &'static str {
|
2026-09-08 00:09:13 +03:00
|
|
|
"UPDATE states SET score = ?1, last_opened = ?2, fsrs_opened = ?3, fsrs_memory = ?4, fsrs_open_day = ?5 WHERE id = ?6"
|
2026-08-30 20:43:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add() -> &'static str {
|
2026-09-08 00:09:13 +03:00
|
|
|
"INSERT INTO states (word_id, deck_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"
|
2026-08-30 20:43:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn name() -> &'static str {
|
|
|
|
|
"states"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn from_sql(row: &Row) -> Result<CardState, Error> {
|
|
|
|
|
Ok(CardState {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
word_id: row.get(1)?,
|
2026-09-06 23:55:50 +03:00
|
|
|
deck_id: row.get(2)?,
|
2026-08-30 20:46:52 +03:00
|
|
|
score: row.get(3)?,
|
|
|
|
|
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(5)?)
|
2026-08-30 20:43:31 +03:00
|
|
|
.expect("Incorrect date encoding"),
|
2026-08-30 20:46:52 +03:00
|
|
|
fsrs_memory: get_memory(row.get(6)?),
|
|
|
|
|
last_open: row.get(4)?,
|
|
|
|
|
fsrs_open_day: NaiveDate::from_epoch_days(row.get::<usize, i32>(7)?)
|
2026-08-30 20:43:31 +03:00
|
|
|
.expect("Incorrect date encoding"),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn insert_params(entity: &CardState) -> impl Params {
|
|
|
|
|
(
|
|
|
|
|
entity.word_id,
|
2026-09-06 23:55:50 +03:00
|
|
|
entity.deck_id,
|
2026-08-30 20:43:31 +03:00
|
|
|
entity.score,
|
|
|
|
|
entity.last_open.timestamp(),
|
|
|
|
|
entity.fsrs_opened.to_epoch_days(),
|
|
|
|
|
memory_to_i64(&entity.fsrs_memory),
|
|
|
|
|
entity.fsrs_open_day.to_epoch_days(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_params(entity: &CardState) -> impl Params {
|
2026-09-08 00:09:13 +03:00
|
|
|
(
|
|
|
|
|
entity.score,
|
|
|
|
|
entity.last_open.timestamp(),
|
2026-09-09 10:08:52 +03:00
|
|
|
entity.fsrs_opened.to_epoch_days(),
|
2026-09-08 00:09:13 +03:00
|
|
|
memory_to_i64(&entity.fsrs_memory),
|
2026-09-09 10:08:52 +03:00
|
|
|
entity.fsrs_open_day.to_epoch_days(),
|
|
|
|
|
entity.id,
|
2026-09-08 00:09:13 +03:00
|
|
|
)
|
2026-08-30 20:43:31 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-08-24 18:16:41 +03:00
|
|
|
pub fn load_stats_of_deck(
|
|
|
|
|
set: &DeckSettings,
|
|
|
|
|
connection: &Connection,
|
2026-08-30 20:43:31 +03:00
|
|
|
) -> Result<Vec<CardState>, NotificationData> {
|
2026-08-01 19:43:07 +03:00
|
|
|
let mut stmt = connection
|
2026-08-29 21:31:54 +03:00
|
|
|
.prepare("SELECT id, word_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states WHERE set_id = ?1")
|
2026-08-24 18:16:41 +03:00
|
|
|
.expect("Failed to prepare state query");
|
2026-08-01 19:43:07 +03:00
|
|
|
let iter = stmt
|
|
|
|
|
.query_map((set.id,), |row| {
|
2026-08-30 20:43:31 +03:00
|
|
|
Ok(CardState {
|
2026-08-01 19:43:07 +03:00
|
|
|
id: row.get(0)?,
|
|
|
|
|
word_id: row.get(1)?,
|
2026-09-06 23:55:50 +03:00
|
|
|
deck_id: set.id,
|
2026-08-01 19:43:07 +03:00
|
|
|
score: row.get(2)?,
|
2026-08-28 18:52:38 +03:00
|
|
|
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(4)?)
|
|
|
|
|
.expect("Incorrect date encoding"),
|
2026-08-30 20:32:16 +03:00
|
|
|
fsrs_memory: get_memory(row.get(5)?),
|
2026-08-01 19:43:07 +03:00
|
|
|
last_open: row.get(3)?,
|
2026-08-28 18:52:38 +03:00
|
|
|
fsrs_open_day: NaiveDate::from_epoch_days(row.get::<usize, i32>(6)?)
|
|
|
|
|
.expect("Incorrect date encoding"),
|
2026-08-01 19:43:07 +03:00
|
|
|
})
|
2026-04-13 22:25:58 +03:00
|
|
|
})
|
2026-08-24 18:16:41 +03:00
|
|
|
.map_err(|_| DbLoadError)?;
|
2026-04-13 22:25:58 +03:00
|
|
|
|
2026-08-24 18:16:41 +03:00
|
|
|
let mut buffer = Vec::with_capacity(512);
|
2026-08-24 18:46:54 +03:00
|
|
|
iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i));
|
2026-04-13 22:25:58 +03:00
|
|
|
|
2026-08-24 18:16:41 +03:00
|
|
|
Ok(buffer)
|
2026-04-13 22:25:58 +03:00
|
|
|
}
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
fn get_memory(value: i64) -> Option<MemoryState> {
|
|
|
|
|
if value == 0 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
Some(MemoryState {
|
2026-08-28 18:52:38 +03:00
|
|
|
stability: f32::from_bits(value as u32),
|
|
|
|
|
difficulty: f32::from_bits((value >> 32) as u32),
|
2026-08-30 20:32:16 +03:00
|
|
|
})
|
2026-08-28 18:52:38 +03:00
|
|
|
}
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
fn memory_to_i64(value: &Option<MemoryState>) -> i64 {
|
|
|
|
|
let Some(value) = &value else {
|
|
|
|
|
return 0;
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-28 18:52:38 +03:00
|
|
|
value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-09 10:08:52 +03:00
|
|
|
impl CardStateRepositoryInner {
|
|
|
|
|
pub fn deck_states(&self, deck_id: Id) -> Query<CardState, CardStateRepositoryInner, CardStateDbLayer> {
|
|
|
|
|
self.query(Arc::new(move |state| {state.deck_id == deck_id}))
|
2026-08-20 16:22:38 +03:00
|
|
|
}
|
2026-09-09 10:08:52 +03:00
|
|
|
}
|