use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, WordOpenMode}; use crate::data::{INVALID_ID, Id}; use crate::data_provider::history::HistoryRepositoryInner; use chrono::{DateTime, Local, NaiveDate, Utc}; use fsrs::MemoryState; use litemap::LiteMap; use log::info; use rhai::Engine; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::sync::Arc; use std::time::Instant; #[derive(Clone, Debug)] pub struct WordData { pub id: Id, pub group_id: Id, pub key: String, pub value: String, pub tags: String, pub additional: LiteMap, } impl EntityData for WordData { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } #[derive(Clone)] pub struct WordGroup { pub id: Id, pub name: String, } impl EntityData for WordGroup { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } impl WordData { pub fn new() -> Self { Self { id: INVALID_ID, key: String::new(), value: String::new(), tags: String::new(), additional: Default::default(), group_id: 1.into(), } } } #[derive(Clone)] pub struct HistoryNote { pub id: Id, pub timestamp: DateTime, pub deck_id: Id, pub word_id: Id, pub answer: WordOpenMode, pub before: u8, pub after: u8, pub fsrs: bool, } impl EntityData for HistoryNote { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } impl HistoryRepositoryInner { pub fn activity_of_deck(&self, id: Id) -> Vec<(NaiveDate, u32)> { let mut query = self.query(Arc::new(move |note| note.deck_id == id)); let values = query.iter(self).collect::>(); let mut counts = vec![]; if !values.is_empty() { let mut date = values[0].timestamp.with_timezone(&Local).date_naive(); let mut count = 0; for note in values { let current_date = note.timestamp.with_timezone(&Local).date_naive(); if date == current_date { count += 1; } else { counts.push((date, count)); date = current_date; count = 0; } } if count != 0 { counts.push((date, count)); } } counts } } #[derive(Clone)] pub struct DeckSettings { pub id: Id, pub name: String, pub forward: String, pub backward: String, pub filter: String, } impl EntityData for DeckSettings { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } impl DeckSettings { pub(crate) fn with_name(name: String) -> DeckSettings { DeckSettings { id: 0.into(), name, forward: String::new(), backward: String::new(), filter: "true".to_string(), } } pub fn check_filter(&self) -> bool { let time = Instant::now(); let engine = Engine::new(); let ast = engine.compile(&self.filter); info!("Compile time is {:?}", time.elapsed()); ast.is_ok() } pub fn require_speech(&self) -> bool { self.forward.contains("speech") || self.backward.contains("speech") } } #[derive(Clone, PartialEq)] pub struct CardState { pub id: Id, pub word_id: Id, pub deck_id: Id, pub last_open: DateTime, pub score: u8, pub fsrs_opened: NaiveDate, pub fsrs_memory: Option, pub fsrs_open_day: NaiveDate, } impl EntityData for CardState { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } impl CardState { pub fn update(&mut self, status: WordOpenMode) { match status { WordOpenMode::Easy => { self.score = (self.calculated_score() + 5.0).round() as u8; } WordOpenMode::Ok => self.score = (self.calculated_score() + 2.0).round() as u8, WordOpenMode::Hard => { self.score = (self.calculated_score() * 0.75).round() as u8; } WordOpenMode::None => { self.score = (self.calculated_score() * 0.4) as u8; } } self.score = self.score.clamp(1, MAX_SCORE); self.last_open = Utc::now(); } pub fn calculated_score(&self) -> f32 { let time = Utc::now() - self.last_open; let days = time.num_days(); let multiplier = FADE_PER_DAY.powi(days as i32); (self.score as f32 * multiplier).max(1.0) } } #[derive(Clone)] pub struct CustomField { pub id: Id, pub name: String, pub identifier: String, } impl EntityData for CustomField { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } #[derive(Clone)] pub struct Exam { pub id: Id, pub timestamp: DateTime, pub deck_id: Id, } impl EntityData for Exam { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } #[derive(Clone)] pub struct ExamEntry{ pub id: Id, pub exam_id: Id, pub word_id: Id, pub status: Option } impl EntityData for ExamEntry { fn id(&self) -> Id { self.id } fn set_id(&mut self, id: Id) { self.id = id; } } pub struct EntityHandle { pub(crate) id: Id, index: u32, pub(crate) _phantom: PhantomData, } impl Clone for EntityHandle { fn clone(&self) -> Self { *self } } impl Copy for EntityHandle {} impl EntityHandle { pub fn new(id: Id, index: usize, real: bool) -> Self { Self { id, index: ((index as u32) << 1 >> 1) | (!real as u32) << 31, _phantom: Default::default(), } } pub fn from_id(id: Id) -> Self { Self::new(id, id.0 as usize, true) } pub fn index(&self) -> usize { (self.index << 1 >> 1) as usize } pub fn set_index(&mut self, index: usize) { self.index = (self.index >> 31 << 31) | ((index as u32) << 1 >> 1); } pub fn real(&self) -> bool { (self.index >> 31) == 0 } pub fn set_real(&mut self, value: bool) { self.index = self.index << 1 >> 1 | (!value as u32) << 31; } } impl Eq for EntityHandle {} impl PartialEq for EntityHandle { fn eq(&self, other: &Self) -> bool { self.real() == other.real() && self.id == other.id } } impl PartialOrd for EntityHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for EntityHandle { fn cmp(&self, other: &Self) -> Ordering { if self.real() && !other.real() { return Ordering::Less; } if !self.real() && other.real() { return Ordering::Greater; } self.id.cmp(&other.id) } } impl Hash for EntityHandle { fn hash(&self, state: &mut H) { self.id.hash(state); self.real().hash(state); } } pub trait EntityData { fn id(&self) -> Id; fn set_id(&mut self, id: Id); }