use crate::app::AppState; use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS}; use crate::core::srs::{ FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, WordOpenMode, WorstWordsSRSModule, }; use crate::data::Id; use crate::data::entities::*; use crate::data::repositories::Repository; use crate::data_provider::card_states::CardStateRepositoryInner; use crate::notification::MaybeError; use chrono::Utc; use litemap::LiteMap; use log::info; use rhai::{Engine, Scope}; use std::collections::HashMap; use std::sync::Arc; use std::time::Instant; #[derive(Clone)] pub struct DeckData { pub(crate) words: Vec>, pub(crate) states: Vec>, current_word_index: Option, state: AppState, order_module: OrderModule, open_mode: OrderMode, settings: DeckSettings, } struct DisplayStyle { properties: LiteMap, } impl DeckData { pub fn new(settings: &DeckSettings, open_mode: OrderMode, state: AppState) -> Self { let states_repo = state.card_states.read(); let mut query = states_repo.deck_states(settings.id); let words = query .iter(&states_repo) .map(|card_state| EntityHandle::from_id(card_state.word_id)) .collect(); let states = query.handles().collect::>>(); let fsrs = Fsrs(FsrsModule::new( Self::set_links(states.as_slice(), &states_repo).as_slice(), )); drop(states_repo); Self { states, words, current_word_index: None, state, order_module: fsrs, open_mode, settings: settings.clone(), } } pub fn next(&mut self) -> (EntityHandle, EntityHandle) { if self.fsrs_finished() { self.switch_order_engine() } let repo = self.state.card_states.read(); let links = Self::set_links(self.states.as_slice(), &repo); let index = match self.order_module { SemiRandomSRS(ref mut module) => module.next(links.as_slice()), RandomSRS(ref mut module) => module.next(links.as_slice()), WorstWordsSRS(ref mut module) => module.next(links.as_slice()), Fsrs(ref mut module) => module.next(links.as_slice()), }; self.current_word_index = Some(index); (self.words[index], self.states[index]) } fn set_links<'a>( set: &[EntityHandle], repo: &'a CardStateRepositoryInner, ) -> Vec<&'a CardState> { set.iter() .map(|h| repo.get_ref_static(*h).unwrap()) .collect::>() } pub fn open(&mut self, status: WordOpenMode) -> MaybeError { if let Some(index) = self.current_word_index { let mut repo = self.state.card_states.write(); let card_state_handle = &mut self.states[index]; let card_state = repo.get_mut(card_state_handle).unwrap(); let old_score = card_state.score; card_state.update(status); let new_score = card_state.score; let mut fsrs = false; match self.order_module { SemiRandomSRS(ref mut module) => { module.open(status, index, card_state); } RandomSRS(ref mut module) => { module.open(status, index, card_state); } WorstWordsSRS(ref mut module) => { module.open(status, index, card_state); } Fsrs(ref mut module) => { module.open(status, index, card_state); fsrs = true; } } let word_id = card_state.word_id; repo.save(card_state_handle)?; let mut history = self.state.history.write(); history.create_now(HistoryNote { id: Default::default(), timestamp: Utc::now(), deck_id: self.settings.id, word_id, answer: status, before: old_score, after: new_score, fsrs, })?; } Ok(()) } fn switch_order_engine(&mut self) { let repo = self.state.card_states.read(); let links = Self::set_links(self.states.as_slice(), &repo); self.order_module = match self.open_mode { OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new(links.as_slice())), OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new(links.as_slice())), OrderMode::FullRandom => RandomSRS(RandomSRSModule::new(links.as_slice())), } } fn fsrs_finished(&self) -> bool { if let Fsrs(ref fsrs) = self.order_module && fsrs.finished() { info!("Fsrs finished. Infinite mode enabled"); return true; } false } pub fn len(&self) -> usize { self.states.len() } pub fn list(deck: &DeckSettings, state: &AppState) -> Vec> { let time = Instant::now(); let list = vec![]; let engine = Engine::new(); let ast = engine.compile(&deck.filter); if let Ok(ast) = ast { let groups = state.word_groups.read().iter().cloned().collect::>(); let dictionary = &state.dictionary.read(); let fields: HashMap = HashMap::from_iter( state .custom_fields .read() .iter() .map(|f| (f.id, f.identifier.clone())), ); let query = dictionary.query_par(Arc::new(move |word| { let mut more = rhai::Map::new(); for kv in &word.additional { more.insert(fields[&kv.0].clone().into(), kv.1.clone().into()); } let mut scope = Scope::new(); let group_name = groups[groups .binary_search_by_key(&word.group_id, |g| g.id) .unwrap()] .name .clone(); 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", group_name); let result = engine.eval_ast_with_scope::(&mut scope, &ast); if let Ok(result) = result { return result; } false })); info!("Collecting available words is {:?}", time.elapsed()); let handles = query.handles().collect(); drop(query); handles } else { list } } }