Code cleanup
This commit is contained in:
1 parent
1ad991667b
commit
f66bf69ef5
32 files changed
+234
-899
No files matched your search
+5
-3
@@ -33,7 +33,9 @@ impl DeckData {
|
||||
.map(|card_state| EntityHandle::from_id(card_state.word_id))
|
||||
.collect();
|
||||
let states = query.handles().collect::<Vec<EntityHandle<CardState>>>();
|
||||
let fsrs = Fsrs(FsrsModule::new(Self::set_links(states.as_slice(), &states_repo).as_slice()));
|
||||
let fsrs = Fsrs(FsrsModule::new(
|
||||
Self::set_links(states.as_slice(), &states_repo).as_slice(),
|
||||
));
|
||||
drop(states_repo);
|
||||
Self {
|
||||
states,
|
||||
@@ -61,7 +63,7 @@ impl DeckData {
|
||||
};
|
||||
|
||||
self.current_word_index = Some(index);
|
||||
(self.words[index].clone(), self.states[index].clone())
|
||||
(self.words[index], self.states[index])
|
||||
}
|
||||
|
||||
fn set_links<'a>(
|
||||
@@ -108,7 +110,7 @@ impl DeckData {
|
||||
timestamp: Utc::now(),
|
||||
deck_id: self.settings.id,
|
||||
word_id,
|
||||
answer: status.clone(),
|
||||
answer: status,
|
||||
before: old_score,
|
||||
after: new_score,
|
||||
fsrs,
|
||||
|
||||
+7
-8
@@ -44,9 +44,7 @@ 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,
|
||||
}
|
||||
Self { basket }
|
||||
}
|
||||
|
||||
fn next(&mut self, set: &[&CardState]) -> usize {
|
||||
@@ -90,16 +88,15 @@ impl SRSModule for SemiRandomSRSModule {
|
||||
.expect("update weights error");
|
||||
}
|
||||
fn new(set: &[&CardState]) -> Self {
|
||||
|
||||
let weights = set
|
||||
.iter()
|
||||
.map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0)
|
||||
.collect::<Vec<f32>>();
|
||||
|
||||
Self{
|
||||
Self {
|
||||
generator: rng(),
|
||||
last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"),
|
||||
history: vec![]
|
||||
last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"),
|
||||
history: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +230,9 @@ impl SRSModule for FsrsModule {
|
||||
updated_word.fsrs_open_day = next_day;
|
||||
updated_word.fsrs_opened = updated_word.last_open.date_naive();
|
||||
|
||||
if (updated_word.fsrs_open_day != today && !first_time) || (first_time && status == WordOpenMode::Easy) {
|
||||
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);
|
||||
let index = self.current_group.iter().position(|x| *x == index).unwrap();
|
||||
|
||||
+7
-13
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -10,7 +11,6 @@ use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use crate::data_provider::history::{HistoryRepository, HistoryRepositoryInner};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WordData {
|
||||
@@ -70,7 +70,7 @@ pub struct HistoryNote {
|
||||
pub answer: WordOpenMode,
|
||||
pub before: u8,
|
||||
pub after: u8,
|
||||
pub fsrs: bool
|
||||
pub fsrs: bool,
|
||||
}
|
||||
|
||||
impl EntityData for HistoryNote {
|
||||
@@ -86,17 +86,16 @@ impl EntityData for HistoryNote {
|
||||
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::<Vec<_>>();
|
||||
let values = query.iter(self).collect::<Vec<_>>();
|
||||
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{
|
||||
if date == current_date {
|
||||
count += 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
counts.push((date, count));
|
||||
date = current_date;
|
||||
count = 0;
|
||||
@@ -209,14 +208,9 @@ pub struct EntityHandle<E> {
|
||||
}
|
||||
|
||||
impl<T> Clone for EntityHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EntityHandle {
|
||||
id: self.id,
|
||||
index: self.index,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
impl<T> Copy for EntityHandle<T> {}
|
||||
impl<T> EntityHandle<T> {
|
||||
pub fn new(id: Id, index: usize, real: bool) -> Self {
|
||||
|
||||
+74
-44
@@ -1,8 +1,9 @@
|
||||
use crate::data::Id;
|
||||
use crate::data::entities::{CardState, EntityData, EntityHandle};
|
||||
use crate::data::entities::{EntityData, EntityHandle};
|
||||
use crate::data_provider::sqlite::SqliteConnection;
|
||||
use crate::helpers::into_result;
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use log::debug;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use rayon::iter::IndexedParallelIterator;
|
||||
use rayon::iter::IntoParallelRefIterator;
|
||||
@@ -12,7 +13,6 @@ use std::marker::PhantomData;
|
||||
use std::ops::{Deref, Index, IndexMut};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use log::debug;
|
||||
|
||||
pub trait Repository<T, D>
|
||||
where
|
||||
@@ -31,13 +31,19 @@ where
|
||||
fn create_now(&mut self, new: T) -> Result<EntityHandle<T>, NotificationData>;
|
||||
fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError;
|
||||
fn delete(&mut self, handle: EntityHandle<T>) -> MaybeError;
|
||||
fn find<F>(&self, predicate: F) -> Option<EntityHandle<T>> where F : Fn(&T) -> bool;
|
||||
fn find<F>(&self, predicate: F) -> Option<EntityHandle<T>>
|
||||
where
|
||||
F: Fn(&T) -> bool;
|
||||
fn init(&mut self) -> MaybeError;
|
||||
fn start(&mut self) -> MaybeError;
|
||||
fn reload(&mut self) -> MaybeError;
|
||||
fn shifts(&self) -> usize;
|
||||
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a;
|
||||
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut T> where T: 'a;
|
||||
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T>
|
||||
where
|
||||
T: 'a;
|
||||
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut T>
|
||||
where
|
||||
T: 'a;
|
||||
}
|
||||
|
||||
struct QueryManger<T, R, D>
|
||||
@@ -88,9 +94,7 @@ where
|
||||
.filter(|(_, q)| {
|
||||
let query = q.lock();
|
||||
let rule = &query.rule;
|
||||
return rule(data);
|
||||
|
||||
false
|
||||
rule(data)
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
@@ -118,7 +122,10 @@ where
|
||||
}
|
||||
|
||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Query<T, R, D> {
|
||||
pub fn iter<'a>(&mut self, data_repository_inner: &'a DataRepositoryInner<T, D>) -> impl Iterator<Item = &'a T>
|
||||
pub fn iter<'a>(
|
||||
&mut self,
|
||||
data_repository_inner: &'a DataRepositoryInner<T, D>,
|
||||
) -> impl Iterator<Item = &'a T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
@@ -130,7 +137,10 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
|
||||
.filter_map(move |e| data_repository_inner.get_ref(e))
|
||||
}
|
||||
|
||||
pub fn iter_with_handles<'a>(&mut self, data_repository_inner: &'a DataRepositoryInner<T, D>) -> impl Iterator<Item = (&'a T, EntityHandle<T>)>
|
||||
pub fn iter_with_handles<'a>(
|
||||
&mut self,
|
||||
data_repository_inner: &'a DataRepositoryInner<T, D>,
|
||||
) -> impl Iterator<Item = (&'a T, EntityHandle<T>)>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
@@ -139,9 +149,9 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
|
||||
}
|
||||
self.cache
|
||||
.iter_mut()
|
||||
.filter_map(move |e| Some((data_repository_inner.get_ref(e)?, e.clone())))
|
||||
.filter_map(move |e| Some((data_repository_inner.get_ref(e)?, *e)))
|
||||
}
|
||||
|
||||
|
||||
pub fn for_each_mut<F>(&mut self, repo: &mut DataRepositoryInner<T, D>, mut f: F)
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
@@ -190,19 +200,26 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
debug!("Query for {} elements (filtered {}) takes {:?}", data_repository.len(), self.cache.len(), time.elapsed());
|
||||
debug!(
|
||||
"Query for {} elements (filtered {}) takes {:?}",
|
||||
data_repository.len(),
|
||||
self.cache.len(),
|
||||
time.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn handles(&self) -> impl Iterator<Item = EntityHandle<T>> {
|
||||
self.cache.iter().cloned()
|
||||
}
|
||||
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Index<usize> for Query<T, R, D> {
|
||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Index<usize>
|
||||
for Query<T, R, D>
|
||||
{
|
||||
type Output = EntityHandle<T>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
@@ -210,7 +227,9 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> In
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> IndexMut<usize> for Query<T, R, D> {
|
||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> IndexMut<usize>
|
||||
for Query<T, R, D>
|
||||
{
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.cache[index]
|
||||
}
|
||||
@@ -221,7 +240,10 @@ where
|
||||
D: BatchedDbLayer<T>,
|
||||
T: EntityData,
|
||||
{
|
||||
fn create_now_many(&mut self, entities: &mut [T]) -> Result<Vec<EntityHandle<T>>, NotificationData>;
|
||||
fn create_now_many(
|
||||
&mut self,
|
||||
entities: &mut [T],
|
||||
) -> Result<Vec<EntityHandle<T>>, NotificationData>;
|
||||
fn save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
|
||||
fn delete_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
|
||||
}
|
||||
@@ -257,10 +279,10 @@ pub struct DataRepositoryInner<T: Clone + EntityData, D: DbLayer<T>> {
|
||||
shifts: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
||||
impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
||||
pub fn new(connection: Arc<Mutex<SqliteConnection>>) -> DataRepository<T, D> {
|
||||
Self{
|
||||
inner: Arc::new(RwLock::new(DataRepositoryInner{
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(DataRepositoryInner {
|
||||
cache: vec![],
|
||||
temporal: vec![],
|
||||
next_temporal: 0,
|
||||
@@ -274,7 +296,6 @@ impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> {
|
||||
type Target = Arc<RwLock<DataRepositoryInner<T, D>>>;
|
||||
|
||||
@@ -283,7 +304,9 @@ impl<T: Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T>> DataRepositoryInner<T, D> {
|
||||
impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T>>
|
||||
DataRepositoryInner<T, D>
|
||||
{
|
||||
pub fn query_par(
|
||||
&self,
|
||||
rule: Arc<dyn Fn(&T) -> bool + Send + Sync>,
|
||||
@@ -306,7 +329,7 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
||||
|
||||
pub fn query(
|
||||
&self,
|
||||
rule: Arc<dyn Fn(& T) -> bool + Send + Sync>,
|
||||
rule: Arc<dyn Fn(&T) -> bool + Send + Sync>,
|
||||
) -> Query<T, DataRepositoryInner<T, D>, D>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -323,9 +346,9 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
||||
|
||||
query
|
||||
}
|
||||
|
||||
|
||||
pub fn empty_query(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
||||
Query{
|
||||
Query {
|
||||
cache: vec![],
|
||||
rule: Arc::new(|_| false),
|
||||
parallel: false,
|
||||
@@ -335,7 +358,7 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
||||
}
|
||||
|
||||
pub fn empty_query_par(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
||||
Query{
|
||||
Query {
|
||||
cache: vec![],
|
||||
rule: Arc::new(|_| false),
|
||||
parallel: true,
|
||||
@@ -433,7 +456,7 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
||||
}
|
||||
|
||||
fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError {
|
||||
let original_state = entity.clone();
|
||||
let original_state = *entity;
|
||||
if entity.real() {
|
||||
let data = self.get_ref(entity).unwrap();
|
||||
self.data_layer
|
||||
@@ -441,12 +464,11 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
||||
} else {
|
||||
let index = fast_position_of(self.temporal.as_slice(), entity).unwrap();
|
||||
let data = &mut self.temporal[index];
|
||||
self.data_layer
|
||||
.add_entity(data, &self.connection.lock())?;
|
||||
self.data_layer.add_entity(data, &self.connection.lock())?;
|
||||
entity.set_real(true);
|
||||
entity.id = data.id();
|
||||
entity.set_index(self.cache.len());
|
||||
self.query_manger.handle_save(original_state, entity, &data);
|
||||
self.query_manger.handle_save(original_state, entity, data);
|
||||
|
||||
let data = self.temporal.remove(index);
|
||||
self.cache.push(data);
|
||||
@@ -477,16 +499,16 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
||||
|
||||
fn find<F>(&self, predicate: F) -> Option<EntityHandle<T>>
|
||||
where
|
||||
F: Fn(&T) -> bool
|
||||
F: Fn(&T) -> bool,
|
||||
{
|
||||
if let Some(index) = self.cache.iter().position(|entity| predicate(entity)){
|
||||
if let Some(index) = self.cache.iter().position(&predicate) {
|
||||
let value = &self.cache[index];
|
||||
return Some(EntityHandle::new(value.id(), index, true))
|
||||
return Some(EntityHandle::new(value.id(), index, true));
|
||||
}
|
||||
|
||||
if let Some(index) = self.temporal.iter().position(|entity| predicate(entity)){
|
||||
if let Some(index) = self.temporal.iter().position(predicate) {
|
||||
let value = &self.temporal[index];
|
||||
return Some(EntityHandle::new(value.id(), index, false))
|
||||
return Some(EntityHandle::new(value.id(), index, false));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -518,16 +540,16 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
||||
self.shifts
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> impl Iterator<Item=&'a T>
|
||||
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T>
|
||||
where
|
||||
T: 'a
|
||||
T: 'a,
|
||||
{
|
||||
self.cache.iter().chain(self.temporal.iter())
|
||||
}
|
||||
|
||||
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item=&'a mut T>
|
||||
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut T>
|
||||
where
|
||||
T: 'a
|
||||
T: 'a,
|
||||
{
|
||||
self.cache.iter_mut().chain(self.temporal.iter_mut())
|
||||
}
|
||||
@@ -535,10 +557,18 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
||||
impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository<T, D>
|
||||
for DataRepositoryInner<T, D>
|
||||
{
|
||||
fn create_now_many(&mut self, entities: &mut [T]) -> Result<Vec<EntityHandle<T>>, NotificationData> {
|
||||
self.data_layer.add_many_entities(entities, &mut self.connection.lock())?;
|
||||
fn create_now_many(
|
||||
&mut self,
|
||||
entities: &mut [T],
|
||||
) -> Result<Vec<EntityHandle<T>>, NotificationData> {
|
||||
self.data_layer
|
||||
.add_many_entities(entities, &mut self.connection.lock())?;
|
||||
let len = self.cache.len();
|
||||
let handles = entities.iter().enumerate().map(|(index, entity)| {EntityHandle::new(entity.id(), index + len, true)}).collect();
|
||||
let handles = entities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, entity)| EntityHandle::new(entity.id(), index + len, true))
|
||||
.collect();
|
||||
self.cache.extend(entities.to_vec());
|
||||
Ok(handles)
|
||||
}
|
||||
@@ -577,7 +607,7 @@ impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository
|
||||
let start = self.cache.len();
|
||||
|
||||
for (i, (handle, data)) in temp.into_iter().zip(&mut temp_values).enumerate() {
|
||||
let old = handle.clone();
|
||||
let old = *handle;
|
||||
handle.id = data.id();
|
||||
handle.set_index(start + i);
|
||||
handle.set_real(true);
|
||||
@@ -641,7 +671,7 @@ where
|
||||
{
|
||||
let mut temp = Vec::with_capacity(handles.len() / 2);
|
||||
let mut cached = Vec::with_capacity(handles.len() / 2);
|
||||
handles.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
handles.sort_by_key(|a| a.id);
|
||||
handles.iter().for_each(|handle| {
|
||||
if handle.real() {
|
||||
cached.push(getter(handle))
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use crate::data::entities::{CardState, DeckSettings};
|
||||
use crate::data::entities::CardState;
|
||||
use crate::data::repositories::{DataRepository, DataRepositoryInner, Query};
|
||||
use crate::data::Id;
|
||||
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
||||
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use chrono::NaiveDate;
|
||||
use fsrs::MemoryState;
|
||||
use log::info;
|
||||
use rusqlite::{Connection, Error, Params, Row, params};
|
||||
use std::time::Instant;
|
||||
use crate::data::Id;
|
||||
use rusqlite::{Error, Params, Row};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type CardStateRepository =
|
||||
DataRepository<CardState, CardStateDbLayer>;
|
||||
pub type CardStateRepositoryInner =
|
||||
DataRepositoryInner<CardState, CardStateDbLayer>;
|
||||
pub type CardStateRepository = DataRepository<CardState, CardStateDbLayer>;
|
||||
pub type CardStateRepositoryInner = DataRepositoryInner<CardState, CardStateDbLayer>;
|
||||
pub type CardStateDbLayer = SqliteDbLayer<CardState, CardStatesQuerySet>;
|
||||
#[derive(Clone)]
|
||||
pub struct CardStatesQuerySet;
|
||||
@@ -73,35 +67,6 @@ impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
|
||||
)
|
||||
}
|
||||
}
|
||||
pub fn load_stats_of_deck(
|
||||
set: &DeckSettings,
|
||||
connection: &Connection,
|
||||
) -> Result<Vec<CardState>, NotificationData> {
|
||||
let mut stmt = connection
|
||||
.prepare("SELECT id, word_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states WHERE set_id = ?1")
|
||||
.expect("Failed to prepare state query");
|
||||
let iter = stmt
|
||||
.query_map((set.id,), |row| {
|
||||
Ok(CardState {
|
||||
id: row.get(0)?,
|
||||
word_id: row.get(1)?,
|
||||
deck_id: set.id,
|
||||
score: row.get(2)?,
|
||||
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(4)?)
|
||||
.expect("Incorrect date encoding"),
|
||||
fsrs_memory: get_memory(row.get(5)?),
|
||||
last_open: row.get(3)?,
|
||||
fsrs_open_day: NaiveDate::from_epoch_days(row.get::<usize, i32>(6)?)
|
||||
.expect("Incorrect date encoding"),
|
||||
})
|
||||
})
|
||||
.map_err(|_| DbLoadError)?;
|
||||
|
||||
let mut buffer = Vec::with_capacity(512);
|
||||
iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i));
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn get_memory(value: i64) -> Option<MemoryState> {
|
||||
if value == 0 {
|
||||
@@ -122,7 +87,10 @@ fn memory_to_i64(value: &Option<MemoryState>) -> i64 {
|
||||
}
|
||||
|
||||
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}))
|
||||
pub fn deck_states(
|
||||
&self,
|
||||
deck_id: Id,
|
||||
) -> Query<CardState, CardStateRepositoryInner, CardStateDbLayer> {
|
||||
self.query(Arc::new(move |state| state.deck_id == deck_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@ use crate::data::repositories::DataRepository;
|
||||
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
||||
use rusqlite::{Error, Params, Row};
|
||||
|
||||
pub type CustomFieldRepository = DataRepository<CustomField, SqliteDbLayer<CustomField, CustomFieldsQuerySet>>;
|
||||
pub type CustomFieldRepository =
|
||||
DataRepository<CustomField, SqliteDbLayer<CustomField, CustomFieldsQuerySet>>;
|
||||
|
||||
pub struct CustomFieldsQuerySet;
|
||||
impl TableAccessQuerySet<WordGroup> for CustomFieldsQuerySet {
|
||||
|
||||
@@ -52,4 +52,4 @@ impl TableAccessQuerySet<DeckSettings> for DeckQuerySet {
|
||||
entity.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl FromSql for WordOpenMode {
|
||||
2 => Ok(WordOpenMode::Ok),
|
||||
3 => Ok(WordOpenMode::Easy),
|
||||
_ => Ok(WordOpenMode::None),
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(WordOpenMode::None)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl ToSql for WordOpenMode {
|
||||
WordOpenMode::Easy => 3,
|
||||
WordOpenMode::Ok => 2,
|
||||
WordOpenMode::Hard => 1,
|
||||
WordOpenMode::None => 0
|
||||
WordOpenMode::None => 0,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::collections::BTreeMap;
|
||||
use crate::helpers::into_result;
|
||||
use crate::notification::NotificationData;
|
||||
use crate::notification::NotificationData::IncorrectAnkiFile;
|
||||
use log::info;
|
||||
use rusqlite::Connection;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImportData(pub(crate) Vec<ImportGroup>);
|
||||
|
||||
@@ -67,7 +67,11 @@ impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q
|
||||
let time = Instant::now();
|
||||
|
||||
let index: u32 = connection
|
||||
.query_row(format!("{} RETURNING {}", Q::add(), Q::id_field()).as_str(), Q::insert_params(entity), |row| row.get(0))
|
||||
.query_row(
|
||||
format!("{} RETURNING {}", Q::add(), Q::id_field()).as_str(),
|
||||
Q::insert_params(entity),
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|e| {
|
||||
error!("{}", e);
|
||||
DbSaveError
|
||||
@@ -104,7 +108,7 @@ impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q
|
||||
.prepare(Q::load())
|
||||
.expect("Failed to prepare state query");
|
||||
let iter = stmt
|
||||
.query_map([], |row| Ok(Q::from_sql(row)?))
|
||||
.query_map([], |row| Q::from_sql(row))
|
||||
.map_err(|e| {
|
||||
error!("{}", e);
|
||||
DbSaveError
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::fs::*;
|
||||
use log::error;
|
||||
use rusqlite::Connection;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use log::error;
|
||||
|
||||
pub fn create_db() {
|
||||
let db_file = db_path();
|
||||
@@ -130,6 +130,7 @@ impl SqliteConnection {
|
||||
}
|
||||
|
||||
impl Drop for SqliteConnection {
|
||||
|
||||
fn drop(&mut self) { self.finish(); }
|
||||
}
|
||||
fn drop(&mut self) {
|
||||
self.finish();
|
||||
}
|
||||
}
|
||||
+3
-172
@@ -1,9 +1,7 @@
|
||||
use crate::data::entities::{WordData, WordGroup};
|
||||
use crate::data::entities::WordData;
|
||||
use crate::data::repositories::{DataRepository, DataRepositoryInner};
|
||||
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
|
||||
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use rusqlite::{Connection, Error, Params, Row, params};
|
||||
use rusqlite::{Error, Params, Row};
|
||||
|
||||
pub type WordRepository = DataRepository<WordData, WordDataLayer>;
|
||||
pub type WordRepositoryInner = DataRepositoryInner<WordData, WordDataLayer>;
|
||||
@@ -58,171 +56,4 @@ impl TableAccessQuerySet<WordData> for WordQuerySet {
|
||||
entity.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_word(word: &mut WordData, connection: &Connection) -> MaybeError {
|
||||
let index : u32 = connection
|
||||
.query_row(
|
||||
"INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
(
|
||||
&word.key,
|
||||
&word.value,
|
||||
&word.tags,
|
||||
serde_json::to_string(&word.additional).unwrap_or_default(),
|
||||
&word.group_id,
|
||||
),
|
||||
|row| row.get(0),
|
||||
).map_err(|_| DbSaveError)?;
|
||||
|
||||
word.id = index.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_words(words: &mut [WordData], connection: &mut Connection) -> MaybeError {
|
||||
let tx = connection.transaction().map_err(|_| DbLoadError)?;
|
||||
let count = words.len();
|
||||
|
||||
{
|
||||
let mut stmt = tx
|
||||
.prepare(
|
||||
"INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
)
|
||||
.expect("Failed to prepare statement");
|
||||
|
||||
for word in words.iter() {
|
||||
stmt.execute(params![
|
||||
word.key,
|
||||
word.value,
|
||||
word.tags,
|
||||
serde_json::to_string(&word.additional).unwrap_or_default(),
|
||||
word.group_id
|
||||
])
|
||||
.map_err(|_| DbSaveError)?;
|
||||
}
|
||||
}
|
||||
|
||||
let last_index: u32 = tx.last_insert_rowid() as u32;
|
||||
tx.commit().map_err(|_| DbSaveError)?;
|
||||
|
||||
let start_index = last_index - (count as u32) + 1;
|
||||
|
||||
for (index, id) in (start_index..=last_index).enumerate() {
|
||||
words[index].id = id.into();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_word(word: &mut WordData, connection: &Connection) -> MaybeError {
|
||||
if !word.id.is_valid() {
|
||||
Ok(add_word(word, connection)?)
|
||||
} else {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE words SET key = ?1, value = ?2, tags = ?3, more = ?4 WHERE id = ?5",
|
||||
(
|
||||
&word.key,
|
||||
&word.value,
|
||||
&word.tags,
|
||||
serde_json::to_string(&word.additional).map_err(|_| DbSaveError)?,
|
||||
&word.id,
|
||||
),
|
||||
)
|
||||
.map_err(|_| DbSaveError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_word(word: &WordData, connection: &Connection) -> MaybeError {
|
||||
if !word.id.is_valid() {
|
||||
return Err(DbSaveError);
|
||||
}
|
||||
connection
|
||||
.execute("DELETE FROM words WHERE id = ?1", (&word.id,))
|
||||
.map_err(|_| DbSaveError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_words(connection: &Connection) -> Result<Vec<WordData>, NotificationData> {
|
||||
let mut stmt = connection
|
||||
.prepare("SELECT id, key, value, tags, more, group_id FROM words")
|
||||
.expect("Failed to prepare statement");
|
||||
let word_iter = stmt
|
||||
.query_map([], |row| {
|
||||
let additional: String = row.get(4)?;
|
||||
Ok(WordData {
|
||||
id: row.get(0)?,
|
||||
key: row.get(1)?,
|
||||
value: row.get(2)?,
|
||||
tags: row.get(3)?,
|
||||
additional: serde_json::from_str(&additional).unwrap_or_default(),
|
||||
group_id: row.get(5)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| DbLoadError)?;
|
||||
|
||||
let mut buffer = Vec::with_capacity(1024);
|
||||
word_iter
|
||||
.filter_map(|res| res.ok())
|
||||
.for_each(|i| buffer.push(i));
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn load_word_groups(connection: &Connection) -> Result<Vec<WordGroup>, NotificationData> {
|
||||
let mut stmt = connection
|
||||
.prepare("SELECT id, name FROM word_groups")
|
||||
.expect("Failed to prepare statement");
|
||||
let group_iter = stmt
|
||||
.query_map([], |row| {
|
||||
Ok(WordGroup {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| DbLoadError)?;
|
||||
|
||||
let mut buffer = Vec::with_capacity(8);
|
||||
group_iter
|
||||
.filter_map(|res| res.ok())
|
||||
.for_each(|i| buffer.push(i));
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn add_group(group: &mut WordGroup, connection: &Connection) -> MaybeError {
|
||||
let index: u32 = connection
|
||||
.query_row(
|
||||
"INSERT INTO word_groups (name) VALUES (?1)",
|
||||
(&group.name,),
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|_| DbSaveError)?;
|
||||
|
||||
group.id = index.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_group(group: &mut WordGroup, connection: &Connection) -> MaybeError {
|
||||
if !group.id.is_valid() {
|
||||
Ok(add_group(group, connection)?)
|
||||
} else {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE word_groups SET name = ?1 WHERE id = ?2",
|
||||
(&group.name, &group.id),
|
||||
)
|
||||
.map_err(|_| DbSaveError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_group(group: &WordGroup, connection: &Connection) -> MaybeError {
|
||||
if !group.id.is_valid() {
|
||||
return Err(DbSaveError);
|
||||
}
|
||||
connection
|
||||
.execute("DELETE FROM word_groups WHERE id = ?1", (&group.id,))
|
||||
.map_err(|_| DbSaveError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,6 @@ pub fn db_meta_path() -> PathBuf {
|
||||
app_data_dir().join("data.meta")
|
||||
}
|
||||
|
||||
pub fn history_dir() -> Result<PathBuf, NotificationData> {
|
||||
let directory = app_data_dir().join("history");
|
||||
if !directory.exists() {
|
||||
fs::create_dir_all(directory.clone()).map_err(|_| PermissionDenied)?;
|
||||
}
|
||||
Ok(directory)
|
||||
}
|
||||
|
||||
pub fn voice_dir() -> Result<PathBuf, NotificationData> {
|
||||
let directory = app_data_dir().join("voice");
|
||||
if !directory.exists() {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::app::{fill_state, AppState};
|
||||
use crate::data_provider::sqlite::{create_db};
|
||||
use crate::app::{AppState, fill_state};
|
||||
use crate::data_provider::sqlite::create_db;
|
||||
use crate::data_provider::web_api::{
|
||||
get_local_version, get_web_version, load_data, set_local_version,
|
||||
};
|
||||
|
||||
+16
-17
@@ -11,9 +11,9 @@ use crate::pages::dictionary::DictionaryMessage::*;
|
||||
use crate::pages::dictionary_quiz::DictionaryQuizState;
|
||||
use crate::pages::import::ImportState;
|
||||
use crate::pages::word::WordState;
|
||||
use crate::{jl_button, push_error};
|
||||
use crate::push_error_task;
|
||||
use crate::styling::*;
|
||||
use crate::{jl_button, push_error};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use iced::alignment::Vertical::Center;
|
||||
@@ -434,15 +434,13 @@ impl DictionaryState {
|
||||
if !data.handle.real() {
|
||||
return button("-")
|
||||
.on_press(WordAction(data.handle))
|
||||
.style(
|
||||
|_x: &Theme, _status: button::Status| Style {
|
||||
background: None,
|
||||
text_color: Color::BLACK,
|
||||
border: Border::default(),
|
||||
shadow: Shadow::default(),
|
||||
snap: false,
|
||||
},
|
||||
)
|
||||
.style(|_x: &Theme, _status: button::Status| Style {
|
||||
background: None,
|
||||
text_color: Color::BLACK,
|
||||
border: Border::default(),
|
||||
shadow: Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.width(15);
|
||||
}
|
||||
|
||||
@@ -512,15 +510,16 @@ impl DictionaryState {
|
||||
let dict = &self.state.dictionary.write();
|
||||
let mut words_query = dict.full_query();
|
||||
let handles = words_query.handles().collect::<Vec<_>>();
|
||||
words_query.iter(dict).zip(handles).for_each(|(element, handle)| {
|
||||
let tags = split_with_coma(element.tags.as_str());
|
||||
tags.iter()
|
||||
.for_each(|tag| {
|
||||
words_query
|
||||
.iter(dict)
|
||||
.zip(handles)
|
||||
.for_each(|(element, handle)| {
|
||||
let tags = split_with_coma(element.tags.as_str());
|
||||
tags.iter().for_each(|tag| {
|
||||
tags_list.insert(tag.clone());
|
||||
});
|
||||
self.words_tags.push((handle, tags))
|
||||
|
||||
});
|
||||
self.words_tags.push((handle, tags))
|
||||
});
|
||||
|
||||
let current_tags = self
|
||||
.tag_map
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::app::AppState;
|
||||
use crate::data::entities::{EntityHandle, WordData};
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::RootMessage;
|
||||
use crate::navigation::*;
|
||||
@@ -6,15 +9,12 @@ use crate::pages::dictionary::split_with_coma;
|
||||
use crate::pages::dictionary_quiz::DictionaryQuizMessage::*;
|
||||
use crate::pages::quiz::Score;
|
||||
use crate::styling::*;
|
||||
use iced::Background::Color;
|
||||
use iced::border::Radius;
|
||||
use iced::widget::container::Style;
|
||||
use iced::widget::{Row, button, container, row, space, text, text_input};
|
||||
use iced::{Border, Element, Fill, Task, Theme, alignment};
|
||||
use iced::widget::{container, row, space, text, text_input, Row};
|
||||
use iced::Background::Color;
|
||||
use iced::{alignment, Border, Element, Fill, Task, Theme};
|
||||
use rand::prelude::SliceRandom;
|
||||
use crate::app::AppState;
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::jl_button;
|
||||
|
||||
pub struct DictionaryQuizState {
|
||||
state: AppState,
|
||||
@@ -91,11 +91,8 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
|
||||
.size(25),
|
||||
]
|
||||
.spacing(DEFAULT_SPACING),
|
||||
row![
|
||||
jl_button!("Закончить").on_press(Back),
|
||||
self.appeal_button()
|
||||
]
|
||||
.spacing(DEFAULT_SPACING),
|
||||
row![jl_button!("Закончить").on_press(Back), self.appeal_button()]
|
||||
.spacing(DEFAULT_SPACING),
|
||||
]
|
||||
.spacing(DEFAULT_SPACING)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
@@ -107,8 +104,13 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
|
||||
}
|
||||
|
||||
impl DictionaryQuizState {
|
||||
pub fn new(words: Vec<EntityHandle<WordData>>, reverse: bool, no_typing: bool, state: AppState) -> DictionaryQuizState {
|
||||
let state = DictionaryQuizState {
|
||||
pub fn new(
|
||||
words: Vec<EntityHandle<WordData>>,
|
||||
reverse: bool,
|
||||
no_typing: bool,
|
||||
state: AppState,
|
||||
) -> DictionaryQuizState {
|
||||
Self {
|
||||
state,
|
||||
words,
|
||||
current_set: Vec::new(),
|
||||
@@ -120,9 +122,7 @@ impl DictionaryQuizState {
|
||||
reverse,
|
||||
laps: 0,
|
||||
no_typing,
|
||||
};
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
fn submit(&mut self) {
|
||||
|
||||
+24
-27
@@ -3,13 +3,13 @@ use crate::data::entities::{HistoryNote, WordData};
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::navigation::*;
|
||||
use crate::styling::*;
|
||||
use HistoryMessage::Back;
|
||||
use chrono::Local;
|
||||
use iced::alignment::Horizontal::Center;
|
||||
use iced::widget::space::horizontal;
|
||||
use iced::widget::*;
|
||||
use iced::{Element, Fill, FillPortion, Task};
|
||||
use std::sync::Arc;
|
||||
use HistoryMessage::Back;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HistoryState {
|
||||
@@ -37,9 +37,9 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
|
||||
.width(FillPortion(5)),
|
||||
horizontal().width(FillPortion(1))
|
||||
]
|
||||
.height(Fill)
|
||||
.width(Fill)
|
||||
.into(),
|
||||
.height(Fill)
|
||||
.width(Fill)
|
||||
.into(),
|
||||
Back,
|
||||
)
|
||||
}
|
||||
@@ -50,23 +50,22 @@ impl HistoryState {
|
||||
let dict = &state.dictionary.read();
|
||||
let history_repo = &mut state.history.write();
|
||||
history_repo.init().expect("TODO: panic message");
|
||||
let id = id.clone();
|
||||
let id = id;
|
||||
let mut history = history_repo.query(Arc::new(move |h| h.deck_id == id));
|
||||
let words = history
|
||||
.iter(history_repo)
|
||||
.map(
|
||||
|item| {let handle = dict.handle_by_id(item.word_id);
|
||||
let word = dict.get_static(handle).unwrap_or(WordData {
|
||||
id: Default::default(),
|
||||
group_id: Default::default(),
|
||||
key: "Удаленное слово".to_string(),
|
||||
value: "".to_string(),
|
||||
tags: "".to_string(),
|
||||
additional: Default::default(),
|
||||
});
|
||||
(word, item.clone())
|
||||
}
|
||||
)
|
||||
.map(|item| {
|
||||
let handle = dict.handle_by_id(item.word_id);
|
||||
let word = dict.get_static(handle).unwrap_or(WordData {
|
||||
id: Default::default(),
|
||||
group_id: Default::default(),
|
||||
key: "Удаленное слово".to_string(),
|
||||
value: "".to_string(),
|
||||
tags: "".to_string(),
|
||||
additional: Default::default(),
|
||||
});
|
||||
(word, item.clone())
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
list: words,
|
||||
@@ -77,30 +76,28 @@ impl HistoryState {
|
||||
fn history_lines(&self) -> Column<'_, HistoryMessage> {
|
||||
let mut column = Column::new();
|
||||
for (index, item) in &self.list {
|
||||
|
||||
let activity = item;
|
||||
column = column.push(
|
||||
row![
|
||||
iced::widget::column![
|
||||
text!("{}", index.key)
|
||||
.width(Fill)
|
||||
.align_x(Center),
|
||||
text!("{}", index.value)
|
||||
.width(Fill)
|
||||
.align_x(Center),
|
||||
text!("{}", index.key).width(Fill).align_x(Center),
|
||||
text!("{}", index.value).width(Fill).align_x(Center),
|
||||
]
|
||||
.align_x(Center),
|
||||
iced::widget::column![
|
||||
text!("{} ➞ {}", activity.before, activity.after),
|
||||
text!(
|
||||
"{}",
|
||||
activity.timestamp.with_timezone(&Local).format("%d.%m %H:%M")
|
||||
activity
|
||||
.timestamp
|
||||
.with_timezone(&Local)
|
||||
.format("%d.%m %H:%M")
|
||||
)
|
||||
]
|
||||
.spacing(5)
|
||||
.align_x(Center)
|
||||
]
|
||||
.spacing(5),
|
||||
.spacing(5),
|
||||
);
|
||||
column = column.push(rule::horizontal(1));
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
use crate::app::AppState;
|
||||
use crate::data::entities::EntityData;
|
||||
use crate::data::entities::WordData;
|
||||
use crate::data::entities::WordGroup;
|
||||
use crate::data::repositories::{BatchedRepository, Repository};
|
||||
use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups};
|
||||
use crate::fs::*;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::notification::NotificationData::OpenFileError;
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use crate::pages::import::ImportMessage::*;
|
||||
use crate::push_error;
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use iced::widget::button::danger;
|
||||
use iced::widget::container::success;
|
||||
@@ -31,7 +31,6 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::spawn_blocking;
|
||||
use zip::ZipArchive;
|
||||
use crate::data::repositories::{BatchedRepository, Repository};
|
||||
|
||||
const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];
|
||||
|
||||
@@ -436,7 +435,8 @@ impl ImportState {
|
||||
async move {
|
||||
spawn_blocking(move || {
|
||||
let temp_file_path = temp_import_path();
|
||||
let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
|
||||
let connection =
|
||||
Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
|
||||
let import_list = get_words_of_group(&connection, group.id)?;
|
||||
let mut words_list = Vec::with_capacity(import_list.len());
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,6 @@
|
||||
use crate::app::USER_FONT;
|
||||
use crate::core::kana_set::KanaSet;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::pages::quiz::QuizMessage::*;
|
||||
@@ -7,7 +8,6 @@ use crate::styling::*;
|
||||
use iced::widget::*;
|
||||
use iced::{Element, Fill, Task, alignment};
|
||||
use rand::seq::SliceRandom;
|
||||
use crate::jl_button;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct QuizState {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::jl_button;
|
||||
use crate::navigation::RootMessage;
|
||||
use crate::navigation::{NavigatedPage, Page};
|
||||
use crate::pages::randomizer::RandomizerMessage::{Back, Edit, Start};
|
||||
use crate::styling::{HALF_SPACING, back_overlay};
|
||||
use crate::jl_button;
|
||||
use iced::Task;
|
||||
use iced::widget::text_editor;
|
||||
use rand::prelude::SliceRandom;
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::data::Id;
|
||||
use crate::data::entities::{CardState, WordData};
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::data_provider::voice::get_voice;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage};
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use crate::pages::repetitions::DeckRunData;
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use crate::{push_error, push_error_task};
|
||||
use iced::alignment::Horizontal::Center;
|
||||
|
||||
@@ -2,11 +2,11 @@ use crate::app::AppState;
|
||||
use crate::core::deck::DeckData;
|
||||
use crate::data::entities::{DeckSettings, EntityHandle};
|
||||
use crate::data::repositories::{BatchedRepository, Repository};
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::notification::MaybeError;
|
||||
use crate::pages::repetition_settings::RepetitionSettingsMessage::*;
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use crate::{push_error, push_error_task};
|
||||
use iced::widget::button::danger;
|
||||
@@ -123,7 +123,10 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
|
||||
}
|
||||
let mut states = self.state.card_states.write();
|
||||
let deck_id = self.settings_handle.id;
|
||||
let mut query = states.query(Arc::new(move |state| {state.deck_id == deck_id})).handles().collect::<Vec<_>>();
|
||||
let mut query = states
|
||||
.query(Arc::new(move |state| state.deck_id == deck_id))
|
||||
.handles()
|
||||
.collect::<Vec<_>>();
|
||||
if let Err(e) = states.delete_many(query.as_mut_slice()) {
|
||||
return push_error_task!(e);
|
||||
}
|
||||
@@ -164,8 +167,7 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
|
||||
primary
|
||||
})
|
||||
.on_input(SetFilter),
|
||||
jl_button!("Проверить фильтр")
|
||||
.on_press(TryFilter),
|
||||
jl_button!("Проверить фильтр").on_press(TryFilter),
|
||||
self.count_view(),
|
||||
]
|
||||
.spacing(QUARTER_SPACING),
|
||||
@@ -211,7 +213,7 @@ fn update_deck_stats(deck: &DeckSettings, state: &AppState) -> MaybeError {
|
||||
let deck_id = deck.id;
|
||||
let mut db_stats = states_repo
|
||||
.query_par(Arc::new(move |s| s.deck_id == deck_id))
|
||||
.iter(&states_repo)
|
||||
.iter(states_repo)
|
||||
.collect::<Vec<_>>();
|
||||
let filtered_words = DeckData::list(deck, state)
|
||||
.iter()
|
||||
|
||||
+13
-19
@@ -3,6 +3,7 @@ use crate::core::deck::DeckData;
|
||||
use crate::core::srs::{AppendMode, OrderMode};
|
||||
use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData};
|
||||
use crate::data::repositories::{BatchedRepository, Repository};
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::notification::MaybeError;
|
||||
@@ -12,21 +13,20 @@ use crate::pages::repetition_settings::RepetitionSettingsState;
|
||||
use crate::pages::repetitions::RepetitionsMessage::*;
|
||||
use crate::push_error;
|
||||
use crate::push_error_task;
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use chrono::{Days, Local, Utc};
|
||||
use hashbrown::HashSet;
|
||||
use iced::widget::button::{danger, Status};
|
||||
pub use iced::widget::button::{Catalog, Style};
|
||||
use iced::widget::button::{Status, danger};
|
||||
use iced::widget::tooltip::Position::Top;
|
||||
use iced::widget::{
|
||||
Column, Row, button, column, container, lazy, radio, row, scrollable, space, svg, text,
|
||||
text_input, tooltip,
|
||||
button, column, container, lazy, radio, row, scrollable, space, svg, text, text_input, tooltip,
|
||||
Column, Row,
|
||||
};
|
||||
use iced::{Background, Border, Center, Color, Element, Fill, Length, Shadow, Task, Theme};
|
||||
use iced_core::Padding;
|
||||
use iced_core::border::Radius;
|
||||
use iced_core::svg::Handle;
|
||||
use iced_core::Padding;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -95,7 +95,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
self.clear_selection();
|
||||
}
|
||||
} else {
|
||||
let _ = self.fill_deck_word_indices();
|
||||
self.fill_deck_word_indices();
|
||||
}
|
||||
}
|
||||
fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
|
||||
@@ -127,7 +127,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
DeleteSet => {
|
||||
let index = self.selected_deck_handle.unwrap();
|
||||
self.clear_selection();
|
||||
let deck = self.decks.remove(&index);
|
||||
self.decks.remove(&index);
|
||||
|
||||
if index.real() {
|
||||
let mut repo = self.state.decks.write();
|
||||
@@ -153,7 +153,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
let mut repo = self.state.decks.write();
|
||||
|
||||
repo.set(handle, deck.general_settings.clone());
|
||||
let old = handle.clone();
|
||||
let old = *handle;
|
||||
if let Err(err) = repo.save(handle) {
|
||||
return push_error_task!(err);
|
||||
}
|
||||
@@ -176,7 +176,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
}
|
||||
TryFilter => {
|
||||
let deck = self.selected_deck().unwrap();
|
||||
let count = DeckData::list(&deck, &self.state).len();
|
||||
let count = DeckData::list(deck, &self.state).len();
|
||||
let deck = self.selected_deck_mut().unwrap();
|
||||
deck.count = Some(count);
|
||||
}
|
||||
@@ -228,9 +228,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
row![
|
||||
column![
|
||||
scrollable(self.decks_list()).height(Fill),
|
||||
jl_button!("Добавить")
|
||||
.width(Fill)
|
||||
.on_press(CreateSet),
|
||||
jl_button!("Добавить").width(Fill).on_press(CreateSet),
|
||||
]
|
||||
.spacing(DEFAULT_SPACING)
|
||||
.width(Length::FillPortion(1)),
|
||||
@@ -256,9 +254,7 @@ impl RepetitionsState {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_decks_from_state(
|
||||
state: &AppState,
|
||||
) -> BTreeMap<EntityHandle<DeckSettings>, DeckRunData> {
|
||||
fn get_decks_from_state(state: &AppState) -> BTreeMap<EntityHandle<DeckSettings>, DeckRunData> {
|
||||
state
|
||||
.decks
|
||||
.read()
|
||||
@@ -331,8 +327,7 @@ impl RepetitionsState {
|
||||
column![
|
||||
text!("Фильтр"),
|
||||
text_input("", &deck.filter).on_input(SetFilter),
|
||||
jl_button!("Проверить фильтр")
|
||||
.on_press(TryFilter),
|
||||
jl_button!("Проверить фильтр").on_press(TryFilter),
|
||||
self.count_view(deck),
|
||||
]
|
||||
.spacing(QUARTER_SPACING),
|
||||
@@ -519,7 +514,7 @@ impl RepetitionsState {
|
||||
fn decks_list(&self) -> Column<'_, RepetitionsMessage> {
|
||||
let mut column = Column::new();
|
||||
|
||||
for (i, (handle, deck)) in self.decks.iter().enumerate() {
|
||||
for (handle, deck) in self.decks.iter() {
|
||||
column = column.push(
|
||||
button(text!("{}", &deck.name))
|
||||
.on_press_with(move || SelectSet(*handle))
|
||||
@@ -565,7 +560,6 @@ impl RepetitionsState {
|
||||
deck: &DeckSettings,
|
||||
indices: impl Iterator<Item = EntityHandle<WordData>>,
|
||||
) -> MaybeError {
|
||||
let words = &state.dictionary;
|
||||
let stats = &mut indices
|
||||
.map(|i| CardState {
|
||||
id: 0.into(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::app::AppState;
|
||||
use crate::core::kana_set::{KanaSet, KanaType};
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::*;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::pages::dictionary::DictionaryState;
|
||||
@@ -12,13 +13,10 @@ use crate::pages::writing::WritingState;
|
||||
use crate::styling::*;
|
||||
use iced::widget::button::{Status, Style};
|
||||
use iced::widget::*;
|
||||
use iced::{Element, Task, alignment};
|
||||
use iced::{alignment, Element, Task};
|
||||
use iced_core::Alignment::Center;
|
||||
use iced_core::Background;
|
||||
use iced_core::Color;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use crate::jl_button;
|
||||
|
||||
pub struct SelectorState {
|
||||
pub set: KanaSet,
|
||||
@@ -82,8 +80,7 @@ impl NavigatedPage<SelectorMessage> for SelectorState {
|
||||
container(
|
||||
iced::widget::column![
|
||||
row![
|
||||
jl_button!("あ ↔ ア")
|
||||
.on_press(SelectorMessage::Change),
|
||||
jl_button!("あ ↔ ア").on_press(SelectorMessage::Change),
|
||||
button("Словарь")
|
||||
.on_press(SelectorMessage::ToDictionary)
|
||||
.style(Self::nav_style),
|
||||
|
||||
+7
-11
@@ -2,11 +2,11 @@ use crate::app::AppState;
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::data_provider::settings::{delete_settings, set_setting};
|
||||
use crate::data_provider::web_api::*;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::notification::{NotificationData, NotificationRequest};
|
||||
use crate::pages::sync::SyncMessage::*;
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use crate::{push_error, push_error_task};
|
||||
use iced::widget::button::danger;
|
||||
@@ -241,10 +241,8 @@ impl SyncState {
|
||||
]
|
||||
} else {
|
||||
column![
|
||||
jl_button!("Создать сохранение")
|
||||
.on_press(InitSync),
|
||||
jl_button!("Вставить ключ из буфера")
|
||||
.on_press(GetKey),
|
||||
jl_button!("Создать сохранение").on_press(InitSync),
|
||||
jl_button!("Вставить ключ из буфера").on_press(GetKey),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -271,12 +269,10 @@ impl SyncState {
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Ошибка при checkpoint WAL: {e}");
|
||||
});
|
||||
connection
|
||||
.execute("VACUUM;", [])
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Ошибка при checkpoint WAL: {e}");
|
||||
0
|
||||
});
|
||||
connection.execute("VACUUM;", []).unwrap_or_else(|e| {
|
||||
error!("Ошибка при checkpoint WAL: {e}");
|
||||
0
|
||||
});
|
||||
connection
|
||||
.execute("PRAGMA optimize;", [])
|
||||
.unwrap_or_else(|e| {
|
||||
|
||||
+2
-10
@@ -1,17 +1,12 @@
|
||||
use crate::app::AppState;
|
||||
use crate::data::entities::{EntityHandle, WordData};
|
||||
use crate::data_provider::words::{delete_word, update_word};
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::pages::word::WordMessage::*;
|
||||
use crate::push_error;
|
||||
use crate::push_error_task;
|
||||
use crate::styling::*;
|
||||
use iced::widget::button::danger;
|
||||
use iced::widget::{button, column, row, rule, scrollable, space, text, text_input};
|
||||
use iced::widget::{button, column, row, space, text, text_input};
|
||||
use iced::{Element, Fill, Task};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WordState {
|
||||
@@ -137,10 +132,7 @@ impl NavigatedPage<WordMessage> for WordState {
|
||||
|
||||
impl WordState {
|
||||
pub fn new(handle: EntityHandle<WordData>, state: AppState) -> Self {
|
||||
Self{
|
||||
state,
|
||||
handle,
|
||||
}
|
||||
Self { state, handle }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::core::kana_set::KanaSet;
|
||||
use crate::jl_button;
|
||||
use crate::navigation::Page::PreviousPage;
|
||||
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
||||
use crate::jl_button;
|
||||
use crate::styling::*;
|
||||
use iced::widget::*;
|
||||
use iced::{Element, Fill, Task, alignment};
|
||||
@@ -46,10 +46,8 @@ impl NavigatedPage<WritingMessage> for WritingState {
|
||||
text!("{}", self.kana).size(48),
|
||||
self.answers(),
|
||||
row![
|
||||
jl_button!(text!("{}", self.next_text))
|
||||
.on_press(WritingMessage::Next),
|
||||
jl_button!("Закончить")
|
||||
.on_press(WritingMessage::Back),
|
||||
jl_button!(text!("{}", self.next_text)).on_press(WritingMessage::Next),
|
||||
jl_button!("Закончить").on_press(WritingMessage::Back),
|
||||
]
|
||||
.spacing(DEFAULT_SPACING)
|
||||
]
|
||||
|
||||
+4
-7
@@ -39,13 +39,10 @@ macro_rules! jl_button {
|
||||
|
||||
pub fn back_overlay<'a, T: Clone + 'a>(content: Element<'a, T>, back_message: T) -> Element<'a, T> {
|
||||
container(
|
||||
iced::widget::column![
|
||||
jl_button!("Назад").on_press(back_message),
|
||||
content
|
||||
]
|
||||
.spacing(DEFAULT_SPACING)
|
||||
.align_x(Left)
|
||||
.width(Fill),
|
||||
iced::widget::column![jl_button!("Назад").on_press(back_message), content]
|
||||
.spacing(DEFAULT_SPACING)
|
||||
.align_x(Left)
|
||||
.width(Fill),
|
||||
)
|
||||
.center_x(Fill)
|
||||
.padding(10)
|
||||
|
||||
+4
-4
@@ -73,13 +73,13 @@ mod mod_tests {
|
||||
#[test]
|
||||
fn id_0() {
|
||||
let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true);
|
||||
assert_eq!(entity.real(), true)
|
||||
assert!(entity.real())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_1() {
|
||||
let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false);
|
||||
assert_eq!(entity.real(), false)
|
||||
assert!(!entity.real())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -97,14 +97,14 @@ mod mod_tests {
|
||||
fn id_5() {
|
||||
let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true);
|
||||
entity.set_real(true);
|
||||
assert_eq!(entity.real(), true)
|
||||
assert!(entity.real())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_6() {
|
||||
let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false);
|
||||
entity.set_real(true);
|
||||
assert_eq!(entity.real(), true)
|
||||
assert!(entity.real())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user