use rayon::iter::ParallelIterator; use rayon::iter::IndexedParallelIterator; use std::sync::Arc; use std::time::Instant; use chrono::{DateTime, Utc}; use log::info; use parking_lot::Mutex; use rayon::prelude::IntoParallelRefIterator; use rhai::{Engine, Scope}; use crate::app::AppState; use crate::core::srs::{OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, WordOpenMode, WorstWordsSRSModule, FADE_PER_DAY, MAX_SCORE}; use crate::data_provider::card_stats::{load_stats_of_deck, update_stat_score}; use crate::data_provider::history::{push_note}; use crate::data::entities::*; use crate::data::Id; #[derive(Clone)] pub struct DeckSettings { pub id: Id, pub name: String, pub forward: String, pub backward: String, pub filter: String, pub count: Option, pub open_mode: OrderMode, } impl DeckSettings { pub(crate) fn with_name(name: String) -> DeckSettings { DeckSettings { id: 0.into(), name, forward: "".to_string(), backward: "".to_string(), filter: "true".to_string(), count: None, open_mode: OrderMode::Default, } } 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 get_word_list(&self, state: &AppState) -> Vec { let time = Instant::now(); let mut list = vec![]; let engine = Engine::new(); let ast = engine.compile(&self.filter); if let Ok(ast) = ast { let groups = &state.word_groups; list = state .dictionary .par_iter() .enumerate() .filter(|(_, word)| { let mut more = rhai::Map::new(); for iced in &word.additional { more.insert(iced.0.clone().into(), iced.1.clone().into()); } let mut scope = Scope::new(); scope .push_constant("id", word.id.0 as i64) .push_constant("key", word.key.clone()) .push_constant("value", word.value.clone()) .push_constant("tags", word.tags.clone()) .push_constant("more", more) .push_constant( "group", groups .iter() .find(|g| g.id == word.group_id) .cloned() .unwrap() .name, ); let result = engine.eval_ast_with_scope::(&mut scope, &ast); result.is_ok() && result.unwrap() }) .map(|(index, _)| index) .collect(); info!("Collecting available words is {:?}", time.elapsed()); list } else { list } } pub fn require_speech(&self) -> bool { self.forward == "speech" || self.backward == "speech" } } #[derive(Clone)] pub struct DeckData { pub(crate) words: Vec, pub(crate) set: Vec, current_word_index: Option, state: Arc>, order_module: OrderModule, settings: DeckSettings, } impl DeckData { pub fn new(settings: &DeckSettings, state: Arc>) -> Self { let state_for = state.clone(); let state_locked = state.lock(); let stats = load_stats_of_deck(settings, &state_locked.connection); let words = stats .iter() .map(|stat| { state_locked .dictionary .binary_search_by_key(&stat.word_id, |w| w.id) .unwrap() }) .map(|index| &state_locked.dictionary[index]) .cloned() .collect(); Self { 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()), }, settings: settings.clone(), } } pub fn next(&mut self) -> (WordData, CardStatistics) { let index = match self.order_module.clone() { OrderModule::SemiRandomSRS(mut module) => { if !module.initialized { module.init(self) } let index = module.next(self); self.order_module = OrderModule::SemiRandomSRS(module); index } OrderModule::RandomSRS(mut module) => { if !module.initialized { module.init(self) } let index = module.next(self); self.order_module = OrderModule::RandomSRS(module); index } OrderModule::WorstWordsSRS(mut module) => { if !module.initialized { module.init(self) } let index = module.next(self); self.order_module = OrderModule::WorstWordsSRS(module); index } }; self.current_word_index = Some(index); (self.words[index].clone(), self.set[index].clone()) } pub fn open(&mut self, status: WordOpenMode) { if self.current_word_index.is_none() { return; } let index = self.current_word_index.unwrap(); let word : &mut CardStatistics = &mut self.set[index]; let old_score = word.score; word.update(status); let new_score = word.score; match self.order_module { OrderModule::SemiRandomSRS(ref mut module) => { module.open(status, index, word); } OrderModule::RandomSRS(ref mut module) => { module.open(status, index, word); } OrderModule::WorstWordsSRS(ref mut module) => { module.open(status, index, word); } } update_stat_score(word, &self.state.lock().connection); let _ = push_note( self.settings.id, HistoryItem { timestamp: Utc::now(), word_id: word.word_id, mode: status, before: old_score, after: new_score, }, ); } pub fn len(&self) -> usize { self.set.len() } } #[derive(Clone, PartialEq)] pub struct CardStatistics { pub id: Id, pub word_id: Id, pub set_id: Id, pub last_open: DateTime, pub score: u8, } impl CardStatistics { 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) } }