Repo integration #15
No files matched your search
@@ -103,6 +103,7 @@ pub fn fill_state(state: &mut AppState) {
|
|||||||
state.dictionary.write().start().unwrap();
|
state.dictionary.write().start().unwrap();
|
||||||
state.decks.write().start().unwrap();
|
state.decks.write().start().unwrap();
|
||||||
state.history.write().start().unwrap();
|
state.history.write().start().unwrap();
|
||||||
|
state.card_states.write().start().unwrap();
|
||||||
|
|
||||||
state.sync_data = Arc::new(RwLock::new(setting))
|
state.sync_data = Arc::new(RwLock::new(setting))
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-94
@@ -1,23 +1,23 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::time::Instant;
|
|
||||||
use chrono::Utc;
|
|
||||||
use crate::app::AppState;
|
use crate::app::AppState;
|
||||||
use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS};
|
use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS};
|
||||||
use crate::core::srs::{
|
use crate::core::srs::{
|
||||||
OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
|
FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
|
||||||
WordOpenMode, WorstWordsSRSModule,
|
WordOpenMode, WorstWordsSRSModule,
|
||||||
};
|
};
|
||||||
use crate::data::entities::*;
|
use crate::data::entities::*;
|
||||||
use crate::data_provider::card_states::{CardStateRepositoryInner};
|
use crate::data::repositories::Repository;
|
||||||
use crate::notification::{MaybeError, NotificationData};
|
use crate::data_provider::card_states::CardStateRepositoryInner;
|
||||||
|
use crate::notification::MaybeError;
|
||||||
|
use chrono::Utc;
|
||||||
use log::info;
|
use log::info;
|
||||||
use rhai::{Engine, Scope};
|
use rhai::{Engine, Scope};
|
||||||
use crate::data::repositories::Repository;
|
use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DeckData {
|
pub struct DeckData {
|
||||||
pub(crate) words: Vec<EntityHandle<WordData>>,
|
pub(crate) words: Vec<EntityHandle<WordData>>,
|
||||||
pub(crate) set: Vec<EntityHandle<CardState>>,
|
pub(crate) states: Vec<EntityHandle<CardState>>,
|
||||||
current_word_index: Option<usize>,
|
current_word_index: Option<usize>,
|
||||||
state: AppState,
|
state: AppState,
|
||||||
order_module: OrderModule,
|
order_module: OrderModule,
|
||||||
@@ -25,80 +25,58 @@ pub struct DeckData {
|
|||||||
settings: DeckSettings,
|
settings: DeckSettings,
|
||||||
}
|
}
|
||||||
impl DeckData {
|
impl DeckData {
|
||||||
pub fn new(
|
pub fn new(settings: &DeckSettings, open_mode: OrderMode, state: AppState) -> Self {
|
||||||
settings: &DeckSettings,
|
let states_repo = state.card_states.read();
|
||||||
state: AppState,
|
let mut query = states_repo.deck_states(settings.id);
|
||||||
) -> Result<Self, NotificationData> {
|
let words = query
|
||||||
todo!()
|
.iter(&states_repo)
|
||||||
// let state_for = state.clone();
|
.map(|card_state| EntityHandle::from_id(card_state.word_id))
|
||||||
//
|
.collect();
|
||||||
// let stats = state (settings, &state.connection.read())?;
|
let states = query.handles().collect::<Vec<EntityHandle<CardState>>>();
|
||||||
// let words = stats
|
let fsrs = Fsrs(FsrsModule::new(Self::set_links(states.as_slice(), &states_repo).as_slice()));
|
||||||
// .iter()
|
drop(states_repo);
|
||||||
// .map(|stat| {
|
Self {
|
||||||
// state
|
states,
|
||||||
// .dictionary
|
words,
|
||||||
// .binary_search_by_key(&stat.word_id, |w| w.id)
|
current_word_index: None,
|
||||||
// .expect("No word found in dictionary")
|
state,
|
||||||
// })
|
order_module: fsrs,
|
||||||
// .map(|index| &state.dictionary[index])
|
open_mode,
|
||||||
// .cloned()
|
settings: settings.clone(),
|
||||||
// .collect();
|
}
|
||||||
//
|
|
||||||
// Ok(Self {
|
|
||||||
// set: stats,
|
|
||||||
// words,
|
|
||||||
// current_word_index: None,
|
|
||||||
// state: state_for,
|
|
||||||
// order_module: Fsrs(FsrsModule::new()),
|
|
||||||
// open_mode: settings.open_mode,
|
|
||||||
// settings: settings.clone(),
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> (EntityHandle<WordData>, EntityHandle<CardState>) {
|
pub fn next(&mut self) -> (EntityHandle<WordData>, EntityHandle<CardState>) {
|
||||||
|
if self.fsrs_finished() {
|
||||||
|
self.switch_order_engine()
|
||||||
|
}
|
||||||
let repo = self.state.card_states.read();
|
let repo = self.state.card_states.read();
|
||||||
let links = Self::set_links(self.set.as_slice(), &repo);
|
let links = Self::set_links(self.states.as_slice(), &repo);
|
||||||
let index = match self.order_module {
|
let index = match self.order_module {
|
||||||
SemiRandomSRS(ref mut module) => {
|
SemiRandomSRS(ref mut module) => module.next(links.as_slice()),
|
||||||
if !module.initialized {
|
|
||||||
module.init(links.as_slice());
|
|
||||||
}
|
|
||||||
module.next(links.as_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
RandomSRS(ref mut module) => {
|
RandomSRS(ref mut module) => module.next(links.as_slice()),
|
||||||
if !module.initialized {
|
WorstWordsSRS(ref mut module) => module.next(links.as_slice()),
|
||||||
module.init(links.as_slice())
|
Fsrs(ref mut module) => module.next(links.as_slice()),
|
||||||
}
|
|
||||||
module.next(links.as_slice())
|
|
||||||
}
|
|
||||||
WorstWordsSRS(ref mut module) => {
|
|
||||||
if !module.initialized {
|
|
||||||
module.init(links.as_slice())
|
|
||||||
}
|
|
||||||
module.next(links.as_slice())
|
|
||||||
}
|
|
||||||
Fsrs(ref mut module) => {
|
|
||||||
if !module.initialized {
|
|
||||||
module.init(links.as_slice())
|
|
||||||
}
|
|
||||||
module.next(links.as_slice())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.current_word_index = Some(index);
|
self.current_word_index = Some(index);
|
||||||
(self.words[index].clone(), self.set[index].clone())
|
(self.words[index].clone(), self.states[index].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_links<'a>(set: &[EntityHandle<CardState>], repo: &'a CardStateRepositoryInner) -> Vec<&'a CardState> {
|
fn set_links<'a>(
|
||||||
set.iter().map(|h| repo.get_ref_static(*h).unwrap()).collect::<Vec<_>>()
|
set: &[EntityHandle<CardState>],
|
||||||
|
repo: &'a CardStateRepositoryInner,
|
||||||
|
) -> Vec<&'a CardState> {
|
||||||
|
set.iter()
|
||||||
|
.map(|h| repo.get_ref_static(*h).unwrap())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(&mut self, status: WordOpenMode) -> MaybeError {
|
pub fn open(&mut self, status: WordOpenMode) -> MaybeError {
|
||||||
if let Some(index) = self.current_word_index {
|
if let Some(index) = self.current_word_index {
|
||||||
let mut repo = self.state.card_states.write();
|
let mut repo = self.state.card_states.write();
|
||||||
let card_state_handle = &mut self.set[index];
|
let card_state_handle = &mut self.states[index];
|
||||||
let card_state = repo.get_mut(card_state_handle).unwrap();
|
let card_state = repo.get_mut(card_state_handle).unwrap();
|
||||||
let old_score = card_state.score;
|
let old_score = card_state.score;
|
||||||
card_state.update(status);
|
card_state.update(status);
|
||||||
@@ -121,16 +99,6 @@ impl DeckData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Fsrs(ref fsrs) = self.order_module
|
|
||||||
&& fsrs.finished()
|
|
||||||
{
|
|
||||||
self.order_module = match self.open_mode {
|
|
||||||
OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new()),
|
|
||||||
OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new()),
|
|
||||||
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let word_id = card_state.word_id;
|
let word_id = card_state.word_id;
|
||||||
repo.save(card_state_handle)?;
|
repo.save(card_state_handle)?;
|
||||||
|
|
||||||
@@ -145,23 +113,34 @@ impl DeckData {
|
|||||||
after: new_score,
|
after: new_score,
|
||||||
fsrs,
|
fsrs,
|
||||||
})?;
|
})?;
|
||||||
todo!("Implement history pushing")
|
|
||||||
// let _ = push_note(
|
|
||||||
// self.settings.id,
|
|
||||||
// HistoryNote {
|
|
||||||
// timestamp: Utc::now(),
|
|
||||||
// word_id: word.word_id,
|
|
||||||
// answer: status,
|
|
||||||
// before: old_score,
|
|
||||||
// after: new_score,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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 {
|
pub fn len(&self) -> usize {
|
||||||
self.set.len()
|
self.states.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(deck: &DeckSettings, state: &AppState) -> Vec<EntityHandle<WordData>> {
|
pub fn list(deck: &DeckSettings, state: &AppState) -> Vec<EntityHandle<WordData>> {
|
||||||
@@ -179,17 +158,18 @@ impl DeckData {
|
|||||||
more.insert(iced.0.clone().into(), iced.1.clone().into());
|
more.insert(iced.0.clone().into(), iced.1.clone().into());
|
||||||
}
|
}
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
let group_name = groups[groups.binary_search_by_key(&word.group_id, |g| g.id).unwrap()].name.clone();
|
let group_name = groups[groups
|
||||||
|
.binary_search_by_key(&word.group_id, |g| g.id)
|
||||||
|
.unwrap()]
|
||||||
|
.name
|
||||||
|
.clone();
|
||||||
scope
|
scope
|
||||||
.push_constant("id", word.id.0 as i64)
|
.push_constant("id", word.id.0 as i64)
|
||||||
.push_constant("key", word.key.clone())
|
.push_constant("key", word.key.clone())
|
||||||
.push_constant("value", word.value.clone())
|
.push_constant("value", word.value.clone())
|
||||||
.push_constant("tags", word.tags.clone())
|
.push_constant("tags", word.tags.clone())
|
||||||
.push_constant("more", more)
|
.push_constant("more", more)
|
||||||
.push_constant(
|
.push_constant("group", group_name);
|
||||||
"group",
|
|
||||||
group_name,
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = engine.eval_ast_with_scope::<bool>(&mut scope, &ast);
|
let result = engine.eval_ast_with_scope::<bool>(&mut scope, &ast);
|
||||||
if let Ok(result) = result {
|
if let Ok(result) = result {
|
||||||
@@ -199,7 +179,7 @@ impl DeckData {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
info!("Collecting available words is {:?}", time.elapsed());
|
info!("Collecting available words is {:?}", time.elapsed());
|
||||||
let handles =query.handles().collect();
|
let handles = query.handles().collect();
|
||||||
drop(query);
|
drop(query);
|
||||||
handles
|
handles
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+50
-66
@@ -1,6 +1,7 @@
|
|||||||
use crate::data::entities::CardState;
|
use crate::data::entities::CardState;
|
||||||
use chrono::{Local, TimeDelta};
|
use chrono::{Local, TimeDelta};
|
||||||
use fsrs::FSRS;
|
use fsrs::FSRS;
|
||||||
|
use log::{debug, info};
|
||||||
use rand::distr::weighted::WeightedIndex;
|
use rand::distr::weighted::WeightedIndex;
|
||||||
use rand::prelude::ThreadRng;
|
use rand::prelude::ThreadRng;
|
||||||
use rand::prelude::{Distribution, SliceRandom};
|
use rand::prelude::{Distribution, SliceRandom};
|
||||||
@@ -31,24 +32,23 @@ pub enum OrderModule {
|
|||||||
Fsrs(FsrsModule),
|
Fsrs(FsrsModule),
|
||||||
}
|
}
|
||||||
pub trait SRSModule {
|
pub trait SRSModule {
|
||||||
|
fn new(set: &[&CardState]) -> Self;
|
||||||
fn next(&mut self, set: &[&CardState]) -> usize;
|
fn next(&mut self, set: &[&CardState]) -> usize;
|
||||||
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardState);
|
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardState);
|
||||||
fn init(&mut self, set: &[&CardState]);
|
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RandomSRSModule {
|
pub struct RandomSRSModule {
|
||||||
basket: Vec<usize>,
|
basket: Vec<usize>,
|
||||||
pub(crate) initialized: bool,
|
|
||||||
}
|
|
||||||
impl RandomSRSModule {
|
|
||||||
pub(crate) fn new() -> RandomSRSModule {
|
|
||||||
Self {
|
|
||||||
basket: vec![],
|
|
||||||
initialized: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl SRSModule for RandomSRSModule {
|
impl SRSModule for RandomSRSModule {
|
||||||
|
fn new(set: &[&CardState]) -> Self {
|
||||||
|
let mut basket = (0..set.len()).collect::<Vec<usize>>();
|
||||||
|
basket.shuffle(&mut rand::rng());
|
||||||
|
Self{
|
||||||
|
basket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn next(&mut self, set: &[&CardState]) -> usize {
|
fn next(&mut self, set: &[&CardState]) -> usize {
|
||||||
match self.basket.pop() {
|
match self.basket.pop() {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
@@ -61,29 +61,12 @@ impl SRSModule for RandomSRSModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
||||||
|
|
||||||
fn init(&mut self, set: &[&CardState]) {
|
|
||||||
self.initialized = true;
|
|
||||||
self.basket = (0..set.len()).collect::<Vec<usize>>();
|
|
||||||
self.basket.shuffle(&mut rand::rng())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SemiRandomSRSModule {
|
pub struct SemiRandomSRSModule {
|
||||||
history: Vec<usize>,
|
history: Vec<usize>,
|
||||||
last_weights: WeightedIndex<f32>,
|
last_weights: WeightedIndex<f32>,
|
||||||
generator: ThreadRng,
|
generator: ThreadRng,
|
||||||
pub(crate) initialized: bool,
|
|
||||||
}
|
|
||||||
impl SemiRandomSRSModule {
|
|
||||||
pub(crate) fn new() -> SemiRandomSRSModule {
|
|
||||||
SemiRandomSRSModule {
|
|
||||||
history: vec![],
|
|
||||||
last_weights: WeightedIndex::new([1.0]).unwrap(),
|
|
||||||
generator: rng(),
|
|
||||||
initialized: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl SRSModule for SemiRandomSRSModule {
|
impl SRSModule for SemiRandomSRSModule {
|
||||||
fn next(&mut self, set: &[&CardState]) -> usize {
|
fn next(&mut self, set: &[&CardState]) -> usize {
|
||||||
@@ -106,13 +89,18 @@ impl SRSModule for SemiRandomSRSModule {
|
|||||||
.update_weights(&[(index, &new_weight)])
|
.update_weights(&[(index, &new_weight)])
|
||||||
.expect("update weights error");
|
.expect("update weights error");
|
||||||
}
|
}
|
||||||
fn init(&mut self, set: &[&CardState]) {
|
fn new(set: &[&CardState]) -> Self {
|
||||||
self.initialized = true;
|
|
||||||
let weights = set
|
let weights = set
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0)
|
.map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0)
|
||||||
.collect::<Vec<f32>>();
|
.collect::<Vec<f32>>();
|
||||||
self.last_weights = WeightedIndex::new(weights).expect("Incorrect weights update");
|
|
||||||
|
Self{
|
||||||
|
generator: rng(),
|
||||||
|
last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"),
|
||||||
|
history: vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SemiRandomSRSModule {
|
impl SemiRandomSRSModule {
|
||||||
@@ -122,7 +110,6 @@ impl SemiRandomSRSModule {
|
|||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WorstWordsSRSModule {
|
pub struct WorstWordsSRSModule {
|
||||||
pub(crate) initialized: bool,
|
|
||||||
pool_size: usize,
|
pool_size: usize,
|
||||||
rounds_remaining: u8,
|
rounds_remaining: u8,
|
||||||
pool: Vec<usize>,
|
pool: Vec<usize>,
|
||||||
@@ -148,14 +135,8 @@ impl SRSModule for WorstWordsSRSModule {
|
|||||||
|
|
||||||
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
||||||
|
|
||||||
fn init(&mut self, _: &[&CardState]) {
|
fn new(_: &[&CardState]) -> Self {
|
||||||
self.initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl WorstWordsSRSModule {
|
|
||||||
pub(crate) fn new() -> WorstWordsSRSModule {
|
|
||||||
WorstWordsSRSModule {
|
WorstWordsSRSModule {
|
||||||
initialized: false,
|
|
||||||
pool_size: PARTIAL_POOL_SIZE,
|
pool_size: PARTIAL_POOL_SIZE,
|
||||||
rounds_count: 2,
|
rounds_count: 2,
|
||||||
rounds_remaining: 0,
|
rounds_remaining: 0,
|
||||||
@@ -163,6 +144,8 @@ impl WorstWordsSRSModule {
|
|||||||
queue: vec![],
|
queue: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl WorstWordsSRSModule {
|
||||||
fn fill_pool(&mut self, set: &[&CardState]) {
|
fn fill_pool(&mut self, set: &[&CardState]) {
|
||||||
let mut sorted = set.iter().enumerate().collect::<Vec<_>>();
|
let mut sorted = set.iter().enumerate().collect::<Vec<_>>();
|
||||||
sorted.sort_by_key(|c| c.1.score);
|
sorted.sort_by_key(|c| c.1.score);
|
||||||
@@ -183,10 +166,31 @@ pub struct FsrsModule {
|
|||||||
queue: Vec<usize>,
|
queue: Vec<usize>,
|
||||||
rng: ThreadRng,
|
rng: ThreadRng,
|
||||||
fsrs: FSRS,
|
fsrs: FSRS,
|
||||||
pub(crate) initialized: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SRSModule for FsrsModule {
|
impl SRSModule for FsrsModule {
|
||||||
|
fn new(set: &[&CardState]) -> Self {
|
||||||
|
let today = Local::now().date_naive();
|
||||||
|
|
||||||
|
let mut new = Self {
|
||||||
|
today_set: set
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, c)| c.fsrs_open_day <= today)
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.collect(),
|
||||||
|
current_group: vec![],
|
||||||
|
queue: vec![],
|
||||||
|
solved: vec![],
|
||||||
|
rng: rng(),
|
||||||
|
fsrs: FSRS::default(),
|
||||||
|
};
|
||||||
|
new.today_set.shuffle(&mut new.rng);
|
||||||
|
new.fill_partial_pool();
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
fn next(&mut self, _: &[&CardState]) -> usize {
|
fn next(&mut self, _: &[&CardState]) -> usize {
|
||||||
if self.current_group.is_empty() {
|
if self.current_group.is_empty() {
|
||||||
self.fill_partial_pool();
|
self.fill_partial_pool();
|
||||||
@@ -222,45 +226,25 @@ impl SRSModule for FsrsModule {
|
|||||||
let offset = ((rng().random::<f32>() - 0.5) * 3.0).round() as i32;
|
let offset = ((rng().random::<f32>() - 0.5) * 3.0).round() as i32;
|
||||||
next_day += TimeDelta::days(offset as i64);
|
next_day += TimeDelta::days(offset as i64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let first_time = updated_word.fsrs_memory.is_none();
|
||||||
|
|
||||||
updated_word.fsrs_memory = Some(next.memory);
|
updated_word.fsrs_memory = Some(next.memory);
|
||||||
updated_word.fsrs_open_day = next_day;
|
updated_word.fsrs_open_day = next_day;
|
||||||
updated_word.fsrs_opened = updated_word.last_open.date_naive();
|
updated_word.fsrs_opened = updated_word.last_open.date_naive();
|
||||||
|
|
||||||
if updated_word.fsrs_open_day != today {
|
if (updated_word.fsrs_open_day != today && !first_time) || (first_time && status == WordOpenMode::Easy) {
|
||||||
|
info!("For today word on index {} solved", index);
|
||||||
self.solved.push(index);
|
self.solved.push(index);
|
||||||
let index = self.current_group.iter().position(|x| *x == index).unwrap();
|
let index = self.current_group.iter().position(|x| *x == index).unwrap();
|
||||||
self.current_group.remove(index);
|
self.current_group.remove(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, set: &[&CardState]) {
|
|
||||||
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 {
|
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) {
|
fn fill_partial_pool(&mut self) {
|
||||||
|
debug!("Filling partial pool");
|
||||||
self.current_group.extend(
|
self.current_group.extend(
|
||||||
self.today_set
|
self.today_set
|
||||||
.iter()
|
.iter()
|
||||||
@@ -281,7 +265,7 @@ impl FsrsModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum WordOpenMode {
|
pub enum WordOpenMode {
|
||||||
Easy,
|
Easy,
|
||||||
Ok,
|
Ok,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
use crate::data::entities::{CardState, DeckSettings};
|
use crate::data::entities::{CardState, DeckSettings};
|
||||||
use crate::data::repositories::{DataRepository, DataRepositoryInner};
|
use crate::data::repositories::{DataRepository, DataRepositoryInner, Query};
|
||||||
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
||||||
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
||||||
use crate::notification::{MaybeError, NotificationData};
|
use crate::notification::{MaybeError, NotificationData};
|
||||||
@@ -8,11 +9,13 @@ use fsrs::MemoryState;
|
|||||||
use log::info;
|
use log::info;
|
||||||
use rusqlite::{Connection, Error, Params, Row, params};
|
use rusqlite::{Connection, Error, Params, Row, params};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use crate::data::Id;
|
||||||
|
|
||||||
pub type CardStateRepository =
|
pub type CardStateRepository =
|
||||||
DataRepository<CardState, SqliteDbLayer<CardState, CardStatesQuerySet>>;
|
DataRepository<CardState, CardStateDbLayer>;
|
||||||
pub type CardStateRepositoryInner =
|
pub type CardStateRepositoryInner =
|
||||||
DataRepositoryInner<CardState, SqliteDbLayer<CardState, CardStatesQuerySet>>;
|
DataRepositoryInner<CardState, CardStateDbLayer>;
|
||||||
|
pub type CardStateDbLayer = SqliteDbLayer<CardState, CardStatesQuerySet>;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CardStatesQuerySet;
|
pub struct CardStatesQuerySet;
|
||||||
impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
|
impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
|
||||||
@@ -63,10 +66,10 @@ impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
|
|||||||
(
|
(
|
||||||
entity.score,
|
entity.score,
|
||||||
entity.last_open.timestamp(),
|
entity.last_open.timestamp(),
|
||||||
entity.id,
|
entity.fsrs_opened.to_epoch_days(),
|
||||||
entity.fsrs_opened,
|
|
||||||
memory_to_i64(&entity.fsrs_memory),
|
memory_to_i64(&entity.fsrs_memory),
|
||||||
entity.fsrs_open_day,
|
entity.fsrs_open_day.to_epoch_days(),
|
||||||
|
entity.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,74 +121,8 @@ fn memory_to_i64(value: &Option<MemoryState>) -> i64 {
|
|||||||
value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32)
|
value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_stat_list(stats: &mut [CardState], connection: &mut Connection) -> MaybeError {
|
impl CardStateRepositoryInner {
|
||||||
let time = Instant::now();
|
pub fn deck_states(&self, deck_id: Id) -> Query<CardState, CardStateRepositoryInner, CardStateDbLayer> {
|
||||||
let tx = connection
|
self.query(Arc::new(move |state| {state.deck_id == deck_id}))
|
||||||
.transaction()
|
|
||||||
.expect("Failed to create transaction");
|
|
||||||
let count = stats.len();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = tx
|
|
||||||
.prepare(
|
|
||||||
"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");
|
|
||||||
|
|
||||||
for stat in stats.iter() {
|
|
||||||
stmt.execute(params![
|
|
||||||
stat.word_id,
|
|
||||||
stat.deck_id,
|
|
||||||
stat.score,
|
|
||||||
stat.last_open.timestamp(),
|
|
||||||
stat.fsrs_opened.to_epoch_days(),
|
|
||||||
memory_to_i64(&stat.fsrs_memory),
|
|
||||||
stat.fsrs_open_day.to_epoch_days()
|
|
||||||
])
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let last_index: u32 = tx.last_insert_rowid() as u32;
|
|
||||||
tx.commit().expect("Failed to commit");
|
|
||||||
|
|
||||||
let start_index = last_index - (count as u32) + 1;
|
|
||||||
|
|
||||||
for (index, id) in (start_index..=last_index).enumerate() {
|
|
||||||
stats[index].id = id.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Added {} cards for {:?}", stats.len(), time.elapsed());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_stat_score(stat: &CardState, connection: &Connection) -> MaybeError {
|
|
||||||
let time = Instant::now();
|
|
||||||
|
|
||||||
connection
|
|
||||||
.execute(
|
|
||||||
"UPDATE states SET score = ?1, last_opened = ?2 WHERE id = ?3",
|
|
||||||
(&stat.score, &stat.last_open.timestamp(), &stat.id),
|
|
||||||
)
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
info!("Updated stat: {}", time.elapsed().as_millis());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_stats(stat: &[CardState], connection: &Connection) -> MaybeError {
|
|
||||||
if stat.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let ids = stat
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.id.into())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ");
|
|
||||||
connection
|
|
||||||
.execute(
|
|
||||||
format!("DELETE FROM states WHERE id in ({ids})").as_str(),
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ impl TableAccessQuerySet<HistoryNote> for HistoryQuerySet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add() -> &'static str {
|
fn add() -> &'static str {
|
||||||
"INSERT INTO history (deck_id, state_id, timestamp, answer, before, after, fsrs) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"
|
"INSERT INTO history (deck_id, word_id, timestamp, answer, before, after, fsrs) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ use crate::app::AppState;
|
|||||||
use crate::core::deck::DeckData;
|
use crate::core::deck::DeckData;
|
||||||
use crate::core::srs::WordOpenMode;
|
use crate::core::srs::WordOpenMode;
|
||||||
use crate::data::Id;
|
use crate::data::Id;
|
||||||
use crate::data::entities::{CardState, DeckSettings, WordData};
|
use crate::data::entities::{CardState, WordData};
|
||||||
|
use crate::data::repositories::Repository;
|
||||||
use crate::data_provider::voice::get_voice;
|
use crate::data_provider::voice::get_voice;
|
||||||
use crate::navigation::Page::PreviousPage;
|
use crate::navigation::Page::PreviousPage;
|
||||||
use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage};
|
use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage};
|
||||||
use crate::notification::{MaybeError, NotificationData};
|
use crate::notification::{MaybeError, NotificationData};
|
||||||
|
use crate::pages::repetitions::DeckRunData;
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use crate::{push_error, push_error_task};
|
use crate::{push_error, push_error_task};
|
||||||
use iced::alignment::Horizontal::Center;
|
use iced::alignment::Horizontal::Center;
|
||||||
@@ -18,11 +20,10 @@ use rodio::MixerDeviceSink;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use crate::data::repositories::Repository;
|
|
||||||
|
|
||||||
pub struct RepetitionState {
|
pub struct RepetitionState {
|
||||||
state: AppState,
|
state: AppState,
|
||||||
pub settings: DeckSettings,
|
pub settings: DeckRunData,
|
||||||
pub set: DeckData,
|
pub set: DeckData,
|
||||||
current_word: WordData,
|
current_word: WordData,
|
||||||
current_statistic: CardState,
|
current_statistic: CardState,
|
||||||
@@ -102,10 +103,10 @@ impl NavigatedPage<RepetitionMessage> for RepetitionState {
|
|||||||
|
|
||||||
impl RepetitionState {
|
impl RepetitionState {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
set: DeckSettings,
|
set: DeckRunData,
|
||||||
state: AppState,
|
state: AppState,
|
||||||
) -> Result<RepetitionState, NotificationData> {
|
) -> Result<RepetitionState, NotificationData> {
|
||||||
let mut card_set = DeckData::new(&set, state.clone())?;
|
let mut card_set = DeckData::new(&set, set.open_mode, state.clone());
|
||||||
let (word, stat) = card_set.next();
|
let (word, stat) = card_set.next();
|
||||||
let word = state.dictionary.read().get_static(word).unwrap();
|
let word = state.dictionary.read().get_static(word).unwrap();
|
||||||
let stat = state.card_states.read().get_static(stat).unwrap();
|
let stat = state.card_states.read().get_static(stat).unwrap();
|
||||||
|
|||||||
+18
-18
@@ -33,7 +33,7 @@ use std::sync::Arc;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RepetitionsState {
|
pub struct RepetitionsState {
|
||||||
selected_deck_handle: Option<EntityHandle<DeckSettings>>,
|
selected_deck_handle: Option<EntityHandle<DeckSettings>>,
|
||||||
decks: BTreeMap<EntityHandle<DeckSettings>, DeckViewData>,
|
decks: BTreeMap<EntityHandle<DeckSettings>, DeckRunData>,
|
||||||
state: AppState,
|
state: AppState,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(repetition_state) =
|
if let Ok(repetition_state) =
|
||||||
RepetitionState::new(selected_set.general_settings.clone(), self.state.clone())
|
RepetitionState::new(selected_set.clone(), self.state.clone())
|
||||||
{
|
{
|
||||||
return Some(Repetition(repetition_state));
|
return Some(Repetition(repetition_state));
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
|||||||
let handle = self.state.decks.write().create(general.clone());
|
let handle = self.state.decks.write().create(general.clone());
|
||||||
self.decks.insert(
|
self.decks.insert(
|
||||||
handle,
|
handle,
|
||||||
DeckViewData {
|
DeckRunData {
|
||||||
settings_handle: handle,
|
settings_handle: handle,
|
||||||
general_settings: general,
|
general_settings: general,
|
||||||
existing_words_indices: Default::default(),
|
existing_words_indices: Default::default(),
|
||||||
@@ -258,7 +258,7 @@ impl RepetitionsState {
|
|||||||
|
|
||||||
fn get_decks_from_state(
|
fn get_decks_from_state(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
) -> BTreeMap<EntityHandle<DeckSettings>, DeckViewData> {
|
) -> BTreeMap<EntityHandle<DeckSettings>, DeckRunData> {
|
||||||
state
|
state
|
||||||
.decks
|
.decks
|
||||||
.read()
|
.read()
|
||||||
@@ -267,7 +267,7 @@ impl RepetitionsState {
|
|||||||
.map(|(index, deck)| {
|
.map(|(index, deck)| {
|
||||||
(
|
(
|
||||||
EntityHandle::new(deck.id, index, true),
|
EntityHandle::new(deck.id, index, true),
|
||||||
DeckViewData {
|
DeckRunData {
|
||||||
settings_handle: EntityHandle::new(deck.id, index, true),
|
settings_handle: EntityHandle::new(deck.id, index, true),
|
||||||
general_settings: deck.clone(),
|
general_settings: deck.clone(),
|
||||||
existing_words_indices: Default::default(),
|
existing_words_indices: Default::default(),
|
||||||
@@ -284,7 +284,7 @@ impl RepetitionsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RepetitionsState {
|
impl RepetitionsState {
|
||||||
fn launch_delete_button(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> {
|
fn launch_delete_button(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> {
|
||||||
if deck.id.is_valid() {
|
if deck.id.is_valid() {
|
||||||
if deck.append_mode == AppendMode::Manual && deck.existing_words_indices.is_empty() {
|
if deck.append_mode == AppendMode::Manual && deck.existing_words_indices.is_empty() {
|
||||||
return text!("Добавьте карточки или измените фильтр").into();
|
return text!("Добавьте карточки или измените фильтр").into();
|
||||||
@@ -468,7 +468,7 @@ impl RepetitionsState {
|
|||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
fn word_append_panel(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> {
|
fn word_append_panel(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> {
|
||||||
column![
|
column![
|
||||||
text!("Режим добавления карточек"),
|
text!("Режим добавления карточек"),
|
||||||
row![
|
row![
|
||||||
@@ -491,7 +491,7 @@ impl RepetitionsState {
|
|||||||
.spacing(HALF_SPACING)
|
.spacing(HALF_SPACING)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
fn adder_panel(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> {
|
fn adder_panel(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> {
|
||||||
match deck.append_mode {
|
match deck.append_mode {
|
||||||
AppendMode::Full => space().into(),
|
AppendMode::Full => space().into(),
|
||||||
AppendMode::Manual => row![
|
AppendMode::Manual => row![
|
||||||
@@ -511,7 +511,7 @@ impl RepetitionsState {
|
|||||||
let available = selected_deck.available_words_indices.len();
|
let available = selected_deck.available_words_indices.len();
|
||||||
text!("{} слова добавлено из {}", now, available).into()
|
text!("{} слова добавлено из {}", now, available).into()
|
||||||
}
|
}
|
||||||
fn count_view(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> {
|
fn count_view(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> {
|
||||||
if let Some(count) = deck.count {
|
if let Some(count) = deck.count {
|
||||||
return text!("Количество слов: {}", count).into();
|
return text!("Количество слов: {}", count).into();
|
||||||
}
|
}
|
||||||
@@ -547,7 +547,7 @@ impl RepetitionsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RepetitionsState {
|
impl RepetitionsState {
|
||||||
fn append_all_words(state: &mut AppState, deck: &DeckViewData) -> MaybeError {
|
fn append_all_words(state: &mut AppState, deck: &DeckRunData) -> MaybeError {
|
||||||
let existing = &deck.existing_words_indices;
|
let existing = &deck.existing_words_indices;
|
||||||
let mut created_set = HashSet::with_capacity(existing.len());
|
let mut created_set = HashSet::with_capacity(existing.len());
|
||||||
existing.iter().for_each(|c| {
|
existing.iter().for_each(|c| {
|
||||||
@@ -593,10 +593,10 @@ impl RepetitionsState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_deck(&self) -> Option<&DeckViewData> {
|
fn selected_deck(&self) -> Option<&DeckRunData> {
|
||||||
self.decks.get(&self.selected_deck_handle?)
|
self.decks.get(&self.selected_deck_handle?)
|
||||||
}
|
}
|
||||||
fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> {
|
fn selected_deck_mut(&mut self) -> Option<&mut DeckRunData> {
|
||||||
self.decks.get_mut(&self.selected_deck_handle?)
|
self.decks.get_mut(&self.selected_deck_handle?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,18 +641,18 @@ pub enum RepetitionsMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DeckViewData {
|
pub struct DeckRunData {
|
||||||
settings_handle: EntityHandle<DeckSettings>,
|
pub settings_handle: EntityHandle<DeckSettings>,
|
||||||
general_settings: DeckSettings,
|
pub general_settings: DeckSettings,
|
||||||
existing_words_indices: Vec<EntityHandle<WordData>>,
|
existing_words_indices: Vec<EntityHandle<WordData>>,
|
||||||
available_words_indices: Vec<EntityHandle<WordData>>,
|
available_words_indices: Vec<EntityHandle<WordData>>,
|
||||||
indices_ready: bool,
|
indices_ready: bool,
|
||||||
append_mode: AppendMode,
|
pub append_mode: AppendMode,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
open_mode: OrderMode,
|
pub open_mode: OrderMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for DeckViewData {
|
impl Deref for DeckRunData {
|
||||||
type Target = DeckSettings;
|
type Target = DeckSettings;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
|||||||
Reference in new issue
Block a user