31 changed files with 215 additions and 884 deletions

No files matched your search

-8
View File
@@ -40,11 +40,3 @@ panic = "abort"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.8.2", features = ["html_reports"] } criterion = { version = "0.8.2", features = ["html_reports"] }
rustc-hash = "2.1.3" rustc-hash = "2.1.3"
[[bench]]
name = "map_bench"
harness = false
[[bench]]
name = "repo_cache_bench"
harness = false
-126
View File
@@ -1,126 +0,0 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::collections::HashMap as StdHashMap;
use std::hint::black_box;
use hashbrown::HashMap as BrownHashMap;
const SIZES: [usize; 3] = [5, 10, 50];
const QUERY_COUNT: usize = 4096;
/// Детерминированный ключ ~17 символов, например "key_9e3779b9_0042".
fn make_key(i: u64) -> String {
let h = i.wrapping_mul(0x9E37_79B9_7F4A_7C15);
format!("key_{:08x}_{:04}", h & 0xFFFF_FFFF, i)
}
// Для длинных ключей (пути/URL) замени на:
// fn make_key(i: u64) -> String {
// format!(
// "/api/v2/users/{i}/settings/visibility_{:08x}",
// i.wrapping_mul(0x9E37_79B9)
// )
// }
fn make_vec(n: usize) -> Vec<(String, u64)> {
(0..n as u64).map(|i| (make_key(i), i)).collect()
}
fn make_std_map(data: &[(String, u64)]) -> StdHashMap<String, u64> {
data.iter().map(|(k, v)| (k.clone(), *v)).collect()
}
fn make_brown_map(data: &[(String, u64)]) -> BrownHashMap<String, u64> {
data.iter().map(|(k, v)| (k.clone(), *v)).collect()
}
fn make_queries(n: usize, with_misses: bool) -> Vec<String> {
let keys: Vec<String> = (0..n as u64).map(make_key).collect();
let missing = "__missing_key__".to_string();
(0..QUERY_COUNT)
.map(|i| {
if with_misses && i % 16 == 15 {
missing.clone()
} else {
let idx = i.wrapping_mul(2_654_435_761) % n;
keys[idx].clone()
}
})
.collect()
}
fn lookup_std(map: &StdHashMap<String, u64>, queries: &[String]) -> u64 {
let mut sum = 0u64;
for q in queries {
match map.get(black_box(q.as_str())) {
Some(v) => sum = sum.wrapping_add(*v),
None => sum = sum.wrapping_add(1),
}
}
black_box(sum)
}
fn lookup_brown(map: &BrownHashMap<String, u64>, queries: &[String]) -> u64 {
let mut sum = 0u64;
for q in queries {
match map.get(black_box(q.as_str())) {
Some(v) => sum = sum.wrapping_add(*v),
None => sum = sum.wrapping_add(1),
}
}
black_box(sum)
}
fn lookup_vec(data: &[(String, u64)], queries: &[String]) -> u64 {
let mut sum = 0u64;
for q in queries {
let q = black_box(q);
match data.iter().find(|(k, _)| k == q) {
Some((_, v)) => sum = sum.wrapping_add(*v),
None => sum = sum.wrapping_add(1),
}
}
black_box(sum)
}
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("map_vs_vec_str");
for n in SIZES {
let vec_data = make_vec(n);
let std_map = make_std_map(&vec_data);
let brown_map = make_brown_map(&vec_data);
let queries_hit = make_queries(n, false);
let queries_mixed = make_queries(n, true);
// std::collections::HashMap (SipHash)
group.bench_function(format!("std_hashmap_hit/{n}"), |b| {
b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_hit)))
});
group.bench_function(format!("std_hashmap_mixed/{n}"), |b| {
b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_mixed)))
});
// hashbrown (SwissTable + foldhash)
group.bench_function(format!("hashbrown_hit/{n}"), |b| {
b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_hit)))
});
group.bench_function(format!("hashbrown_mixed/{n}"), |b| {
b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_mixed)))
});
// Vec<(String, u64)>, линейный поиск
group.bench_function(format!("vec_linear_hit/{n}"), |b| {
b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_hit)))
});
group.bench_function(format!("vec_linear_mixed/{n}"), |b| {
b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_mixed)))
});
}
group.finish();
}
criterion_group!(benches, bench);
criterion_main!(benches);
-325
View File
@@ -1,325 +0,0 @@
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use hashbrown::HashMap as HashbrownMap;
use rustc_hash::FxHashMap;
use std::collections::{BTreeMap, HashMap};
use std::hint::black_box;
// Тестовая структура (аналог вашей сущности)
#[derive(Debug, Clone)]
struct Entity {
id: i64,
data: [u8; 64], // 64 байта данных для реалистичности
}
impl Entity {
fn new(id: i64) -> Self {
Self {
id,
data: [id as u8; 64],
}
}
}
// ========== ТЕСТОВЫЕ ФУНКЦИИ ==========
fn bench_vec_insert_sorted(vec: &mut Vec<Entity>, entities: &[Entity]) {
vec.clear();
for entity in entities {
vec.push(entity.clone());
}
// Vec уже отсортирован, так как ID монотонно возрастают
}
fn bench_vec_search_separated<'a>(ids: &[i64], vec: &'a [Entity], id: i64) -> Option<&'a Entity> {
match ids.binary_search(&id) {
Ok(index) => Some(&vec[index]),
Err(_) => None,
}
}
fn bench_vec_search(vec: &Vec<Entity>, id: i64) -> Option<&Entity> {
match vec.binary_search_by(|probe| probe.id.cmp(&id)) {
Ok(index) => Some(&vec[index]),
Err(_) => None,
}
}
fn bench_btreemap_insert(map: &mut BTreeMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_btreemap_search(map: &BTreeMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_hashmap_insert(map: &mut HashMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_hashmap_search(map: &HashMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_hashbrown_insert(map: &mut HashbrownMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_hashbrown_search(map: &HashbrownMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_fxhashmap_insert(map: &mut FxHashMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_fxhashmap_search(map: &FxHashMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
// ========== ГЕНЕРАЦИЯ ДАННЫХ ==========
fn generate_entities(count: usize) -> Vec<Entity> {
(0..count).map(|i| Entity::new(i as i64)).collect()
}
fn generate_search_ids(count: usize, max_id: i64) -> Vec<i64> {
// 70% попаданий, 30% промахов (реалистичный сценарий)
let mut ids = Vec::with_capacity(count);
for i in 0..count {
if i % 10 < 7 {
// Попадание
ids.push((i as i64 * 13) % max_id);
} else {
// Промах (несуществующий ID)
ids.push(max_id + i as i64);
}
}
ids
}
// ========== БЕНЧМАРКИ ==========
fn bench_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("insert");
for size in [10_000, 100_000, 500_000] {
let entities = generate_entities(size);
group.bench_with_input(
BenchmarkId::new("Vec (sorted)", size),
&entities,
|b, entities| {
let mut vec = Vec::with_capacity(size);
b.iter(|| {
bench_vec_insert_sorted(&mut vec, black_box(entities));
});
},
);
group.bench_with_input(
BenchmarkId::new("BTreeMap", size),
&entities,
|b, entities| {
let mut map = BTreeMap::new();
b.iter(|| {
bench_btreemap_insert(&mut map, black_box(entities));
});
},
);
group.bench_with_input(
BenchmarkId::new("HashMap (std)", size),
&entities,
|b, entities| {
let mut map = HashMap::new();
b.iter(|| {
bench_hashmap_insert(&mut map, black_box(entities));
});
},
);
group.bench_with_input(
BenchmarkId::new("HashMap (hashbrown)", size),
&entities,
|b, entities| {
let mut map = HashbrownMap::new();
b.iter(|| {
bench_hashbrown_insert(&mut map, black_box(entities));
});
},
);
group.bench_with_input(
BenchmarkId::new("FxHashMap", size),
&entities,
|b, entities| {
let mut map = FxHashMap::default();
b.iter(|| {
bench_fxhashmap_insert(&mut map, black_box(entities));
});
},
);
}
group.finish();
}
fn bench_search(c: &mut Criterion) {
let mut group = c.benchmark_group("search");
for size in [10_000, 100_000, 500_000] {
let entities = generate_entities(size);
let entity_ids = entities.iter().map(|entity| entity.id).collect::<Vec<_>>();
let search_ids = generate_search_ids(1000, size as i64);
// Подготовка коллекций
let mut vec = Vec::with_capacity(size);
bench_vec_insert_sorted(&mut vec, &entities);
let mut btreemap = BTreeMap::new();
bench_btreemap_insert(&mut btreemap, &entities);
let mut hashmap = HashMap::new();
bench_hashmap_insert(&mut hashmap, &entities);
let mut hashbrown = HashbrownMap::new();
bench_hashbrown_insert(&mut hashbrown, &entities);
let mut fxhashmap = FxHashMap::default();
bench_fxhashmap_insert(&mut fxhashmap, &entities);
group.bench_with_input(
BenchmarkId::new("Vec (binary search)", size),
&search_ids,
|b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_vec_search_separated(&entity_ids, &vec, black_box(id)));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("Vec (binary search) separated", size),
&search_ids,
|b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_vec_search(&vec, black_box(id)));
}
});
},
);
group.bench_with_input(BenchmarkId::new("BTreeMap", size), &search_ids, |b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_btreemap_search(&btreemap, black_box(id)));
}
});
});
group.bench_with_input(
BenchmarkId::new("HashMap (std)", size),
&search_ids,
|b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_hashmap_search(&hashmap, black_box(id)));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("HashMap (hashbrown)", size),
&search_ids,
|b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_hashbrown_search(&hashbrown, black_box(id)));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("FxHashMap", size),
&search_ids,
|b, ids| {
b.iter(|| {
for &id in ids {
black_box(bench_fxhashmap_search(&fxhashmap, black_box(id)));
}
});
},
);
}
group.finish();
}
fn bench_memory_efficiency(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_efficiency");
let size = 100_000;
let entities = generate_entities(size);
group.bench_function("Vec memory", |b| {
b.iter(|| {
let mut vec = Vec::with_capacity(size);
bench_vec_insert_sorted(&mut vec, black_box(&entities));
black_box(vec);
});
});
group.bench_function("BTreeMap memory", |b| {
b.iter(|| {
let mut map = BTreeMap::new();
bench_btreemap_insert(&mut map, black_box(&entities));
black_box(map);
});
});
group.bench_function("HashMap (std) memory", |b| {
b.iter(|| {
let mut map = HashMap::new();
bench_hashmap_insert(&mut map, black_box(&entities));
black_box(map);
});
});
group.bench_function("HashMap (hashbrown) memory", |b| {
b.iter(|| {
let mut map = HashbrownMap::new();
bench_hashbrown_insert(&mut map, black_box(&entities));
black_box(map);
});
});
group.bench_function("FxHashMap memory", |b| {
b.iter(|| {
let mut map = FxHashMap::default();
bench_fxhashmap_insert(&mut map, black_box(&entities));
black_box(map);
});
});
group.finish();
}
criterion_group!(benches, bench_insert, bench_search, bench_memory_efficiency);
criterion_main!(benches);
+5 -3
View File
@@ -33,7 +33,9 @@ impl DeckData {
.map(|card_state| EntityHandle::from_id(card_state.word_id)) .map(|card_state| EntityHandle::from_id(card_state.word_id))
.collect(); .collect();
let states = query.handles().collect::<Vec<EntityHandle<CardState>>>(); 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); drop(states_repo);
Self { Self {
states, states,
@@ -61,7 +63,7 @@ impl DeckData {
}; };
self.current_word_index = Some(index); self.current_word_index = Some(index);
(self.words[index].clone(), self.states[index].clone()) (self.words[index], self.states[index])
} }
fn set_links<'a>( fn set_links<'a>(
@@ -108,7 +110,7 @@ impl DeckData {
timestamp: Utc::now(), timestamp: Utc::now(),
deck_id: self.settings.id, deck_id: self.settings.id,
word_id, word_id,
answer: status.clone(), answer: status,
before: old_score, before: old_score,
after: new_score, after: new_score,
fsrs, fsrs,
+5 -6
View File
@@ -44,9 +44,7 @@ impl SRSModule for RandomSRSModule {
fn new(set: &[&CardState]) -> Self { fn new(set: &[&CardState]) -> Self {
let mut basket = (0..set.len()).collect::<Vec<usize>>(); let mut basket = (0..set.len()).collect::<Vec<usize>>();
basket.shuffle(&mut rand::rng()); basket.shuffle(&mut rand::rng());
Self{ Self { basket }
basket,
}
} }
fn next(&mut self, set: &[&CardState]) -> usize { fn next(&mut self, set: &[&CardState]) -> usize {
@@ -90,7 +88,6 @@ impl SRSModule for SemiRandomSRSModule {
.expect("update weights error"); .expect("update weights error");
} }
fn new(set: &[&CardState]) -> Self { fn new(set: &[&CardState]) -> Self {
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)
@@ -99,7 +96,7 @@ impl SRSModule for SemiRandomSRSModule {
Self { Self {
generator: rng(), generator: rng(),
last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"), last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"),
history: vec![] history: vec![],
} }
} }
} }
@@ -233,7 +230,9 @@ impl SRSModule for FsrsModule {
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 && !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); 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();
+6 -12
View File
@@ -1,5 +1,6 @@
use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, WordOpenMode}; use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, WordOpenMode};
use crate::data::{INVALID_ID, Id}; use crate::data::{INVALID_ID, Id};
use crate::data_provider::history::HistoryRepositoryInner;
use chrono::{DateTime, Local, NaiveDate, Utc}; use chrono::{DateTime, Local, NaiveDate, Utc};
use fsrs::MemoryState; use fsrs::MemoryState;
use litemap::LiteMap; use litemap::LiteMap;
@@ -10,7 +11,6 @@ use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use crate::data_provider::history::{HistoryRepository, HistoryRepositoryInner};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct WordData { pub struct WordData {
@@ -70,7 +70,7 @@ pub struct HistoryNote {
pub answer: WordOpenMode, pub answer: WordOpenMode,
pub before: u8, pub before: u8,
pub after: u8, pub after: u8,
pub fsrs: bool pub fsrs: bool,
} }
impl EntityData for HistoryNote { impl EntityData for HistoryNote {
@@ -86,7 +86,7 @@ impl EntityData for HistoryNote {
impl HistoryRepositoryInner { impl HistoryRepositoryInner {
pub fn activity_of_deck(&self, id: Id) -> Vec<(NaiveDate, u32)> { 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 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![]; let mut counts = vec![];
if !values.is_empty() { if !values.is_empty() {
let mut date = values[0].timestamp.with_timezone(&Local).date_naive(); let mut date = values[0].timestamp.with_timezone(&Local).date_naive();
@@ -95,8 +95,7 @@ impl HistoryRepositoryInner {
let current_date = note.timestamp.with_timezone(&Local).date_naive(); let current_date = note.timestamp.with_timezone(&Local).date_naive();
if date == current_date { if date == current_date {
count += 1; count += 1;
} } else {
else {
counts.push((date, count)); counts.push((date, count));
date = current_date; date = current_date;
count = 0; count = 0;
@@ -209,14 +208,9 @@ pub struct EntityHandle<E> {
} }
impl<T> Clone for EntityHandle<T> { impl<T> Clone for EntityHandle<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self { *self }
EntityHandle {
id: self.id,
index: self.index,
_phantom: Default::default(),
}
}
} }
impl<T> Copy for EntityHandle<T> {} impl<T> Copy for EntityHandle<T> {}
impl<T> EntityHandle<T> { impl<T> EntityHandle<T> {
pub fn new(id: Id, index: usize, real: bool) -> Self { pub fn new(id: Id, index: usize, real: bool) -> Self {
+63 -33
View File
@@ -1,8 +1,9 @@
use crate::data::Id; 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::data_provider::sqlite::SqliteConnection;
use crate::helpers::into_result; use crate::helpers::into_result;
use crate::notification::{MaybeError, NotificationData}; use crate::notification::{MaybeError, NotificationData};
use log::debug;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use rayon::iter::IndexedParallelIterator; use rayon::iter::IndexedParallelIterator;
use rayon::iter::IntoParallelRefIterator; use rayon::iter::IntoParallelRefIterator;
@@ -12,7 +13,6 @@ use std::marker::PhantomData;
use std::ops::{Deref, Index, IndexMut}; use std::ops::{Deref, Index, IndexMut};
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use log::debug;
pub trait Repository<T, D> pub trait Repository<T, D>
where where
@@ -31,13 +31,19 @@ where
fn create_now(&mut self, new: T) -> Result<EntityHandle<T>, NotificationData>; fn create_now(&mut self, new: T) -> Result<EntityHandle<T>, NotificationData>;
fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError; fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError;
fn delete(&mut self, handle: 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 init(&mut self) -> MaybeError;
fn start(&mut self) -> MaybeError; fn start(&mut self) -> MaybeError;
fn reload(&mut self) -> MaybeError; fn reload(&mut self) -> MaybeError;
fn shifts(&self) -> usize; fn shifts(&self) -> usize;
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a; fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T>
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut T> where T: 'a; where
T: 'a;
fn iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut T>
where
T: 'a;
} }
struct QueryManger<T, R, D> struct QueryManger<T, R, D>
@@ -88,9 +94,7 @@ where
.filter(|(_, q)| { .filter(|(_, q)| {
let query = q.lock(); let query = q.lock();
let rule = &query.rule; let rule = &query.rule;
return rule(data); rule(data)
false
}) })
.map(|(i, _)| i) .map(|(i, _)| i)
.collect() .collect()
@@ -118,7 +122,10 @@ where
} }
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Query<T, R, D> { 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 where
T: 'a, 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)) .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 where
T: 'a, T: 'a,
{ {
@@ -139,7 +149,7 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
} }
self.cache self.cache
.iter_mut() .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) pub fn for_each_mut<F>(&mut self, repo: &mut DataRepositoryInner<T, D>, mut f: F)
@@ -190,7 +200,12 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
.collect::<Vec<_>>(), .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>> { pub fn handles(&self) -> impl Iterator<Item = EntityHandle<T>> {
@@ -202,7 +217,9 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
} }
} }
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>; type Output = EntityHandle<T>;
fn index(&self, index: usize) -> &Self::Output { 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 { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.cache[index] &mut self.cache[index]
} }
@@ -221,7 +240,10 @@ where
D: BatchedDbLayer<T>, D: BatchedDbLayer<T>,
T: EntityData, 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 save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
fn delete_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError; fn delete_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
} }
@@ -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> { impl<T: Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> {
type Target = Arc<RwLock<DataRepositoryInner<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( pub fn query_par(
&self, &self,
rule: Arc<dyn Fn(&T) -> bool + Send + Sync>, rule: Arc<dyn Fn(&T) -> bool + Send + Sync>,
@@ -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 { fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError {
let original_state = entity.clone(); let original_state = *entity;
if entity.real() { if entity.real() {
let data = self.get_ref(entity).unwrap(); let data = self.get_ref(entity).unwrap();
self.data_layer self.data_layer
@@ -441,12 +464,11 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
} else { } else {
let index = fast_position_of(self.temporal.as_slice(), entity).unwrap(); let index = fast_position_of(self.temporal.as_slice(), entity).unwrap();
let data = &mut self.temporal[index]; let data = &mut self.temporal[index];
self.data_layer self.data_layer.add_entity(data, &self.connection.lock())?;
.add_entity(data, &self.connection.lock())?;
entity.set_real(true); entity.set_real(true);
entity.id = data.id(); entity.id = data.id();
entity.set_index(self.cache.len()); 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); let data = self.temporal.remove(index);
self.cache.push(data); 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>> fn find<F>(&self, predicate: F) -> Option<EntityHandle<T>>
where 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]; 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]; let value = &self.temporal[index];
return Some(EntityHandle::new(value.id(), index, false)) return Some(EntityHandle::new(value.id(), index, false));
} }
None None
} }
@@ -520,14 +542,14 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T> fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T>
where where
T: 'a T: 'a,
{ {
self.cache.iter().chain(self.temporal.iter()) 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 where
T: 'a T: 'a,
{ {
self.cache.iter_mut().chain(self.temporal.iter_mut()) 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> impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository<T, D>
for DataRepositoryInner<T, D> for DataRepositoryInner<T, D>
{ {
fn create_now_many(&mut self, entities: &mut [T]) -> Result<Vec<EntityHandle<T>>, NotificationData> { fn create_now_many(
self.data_layer.add_many_entities(entities, &mut self.connection.lock())?; &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 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()); self.cache.extend(entities.to_vec());
Ok(handles) Ok(handles)
} }
@@ -577,7 +607,7 @@ impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository
let start = self.cache.len(); let start = self.cache.len();
for (i, (handle, data)) in temp.into_iter().zip(&mut temp_values).enumerate() { 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.id = data.id();
handle.set_index(start + i); handle.set_index(start + i);
handle.set_real(true); handle.set_real(true);
@@ -641,7 +671,7 @@ where
{ {
let mut temp = Vec::with_capacity(handles.len() / 2); let mut temp = Vec::with_capacity(handles.len() / 2);
let mut cached = 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| { handles.iter().for_each(|handle| {
if handle.real() { if handle.real() {
cached.push(getter(handle)) cached.push(getter(handle))
+11 -43
View File
@@ -1,20 +1,14 @@
use std::sync::Arc; use crate::data::entities::CardState;
use crate::data::entities::{CardState, DeckSettings};
use crate::data::repositories::{DataRepository, DataRepositoryInner, Query}; use crate::data::repositories::{DataRepository, DataRepositoryInner, Query};
use crate::data::Id;
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
use crate::notification::{MaybeError, NotificationData};
use chrono::NaiveDate; use chrono::NaiveDate;
use fsrs::MemoryState; use fsrs::MemoryState;
use log::info; use rusqlite::{Error, Params, Row};
use rusqlite::{Connection, Error, Params, Row, params}; use std::sync::Arc;
use std::time::Instant;
use crate::data::Id;
pub type CardStateRepository = pub type CardStateRepository = DataRepository<CardState, CardStateDbLayer>;
DataRepository<CardState, CardStateDbLayer>; pub type CardStateRepositoryInner = DataRepositoryInner<CardState, CardStateDbLayer>;
pub type CardStateRepositoryInner =
DataRepositoryInner<CardState, CardStateDbLayer>;
pub type CardStateDbLayer = SqliteDbLayer<CardState, CardStatesQuerySet>; pub type CardStateDbLayer = SqliteDbLayer<CardState, CardStatesQuerySet>;
#[derive(Clone)] #[derive(Clone)]
pub struct CardStatesQuerySet; 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> { fn get_memory(value: i64) -> Option<MemoryState> {
if value == 0 { if value == 0 {
@@ -122,7 +87,10 @@ fn memory_to_i64(value: &Option<MemoryState>) -> i64 {
} }
impl CardStateRepositoryInner { impl CardStateRepositoryInner {
pub fn deck_states(&self, deck_id: Id) -> Query<CardState, CardStateRepositoryInner, CardStateDbLayer> { pub fn deck_states(
self.query(Arc::new(move |state| {state.deck_id == deck_id})) &self,
deck_id: Id,
) -> Query<CardState, CardStateRepositoryInner, CardStateDbLayer> {
self.query(Arc::new(move |state| state.deck_id == deck_id))
} }
} }
+2 -1
View File
@@ -3,7 +3,8 @@ use crate::data::repositories::DataRepository;
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
use rusqlite::{Error, Params, Row}; use rusqlite::{Error, Params, Row};
pub type CustomFieldRepository = DataRepository<CustomField, SqliteDbLayer<CustomField, CustomFieldsQuerySet>>; pub type CustomFieldRepository =
DataRepository<CustomField, SqliteDbLayer<CustomField, CustomFieldsQuerySet>>;
pub struct CustomFieldsQuerySet; pub struct CustomFieldsQuerySet;
impl TableAccessQuerySet<WordGroup> for CustomFieldsQuerySet { impl TableAccessQuerySet<WordGroup> for CustomFieldsQuerySet {
+2 -2
View File
@@ -77,7 +77,7 @@ impl FromSql for WordOpenMode {
2 => Ok(WordOpenMode::Ok), 2 => Ok(WordOpenMode::Ok),
3 => Ok(WordOpenMode::Easy), 3 => Ok(WordOpenMode::Easy),
_ => Ok(WordOpenMode::None), _ => Ok(WordOpenMode::None),
} };
} }
Ok(WordOpenMode::None) Ok(WordOpenMode::None)
} }
@@ -89,7 +89,7 @@ impl ToSql for WordOpenMode {
WordOpenMode::Easy => 3, WordOpenMode::Easy => 3,
WordOpenMode::Ok => 2, WordOpenMode::Ok => 2,
WordOpenMode::Hard => 1, WordOpenMode::Hard => 1,
WordOpenMode::None => 0 WordOpenMode::None => 0,
})) }))
} }
} }
+1 -1
View File
@@ -1,10 +1,10 @@
use std::collections::BTreeMap;
use crate::helpers::into_result; use crate::helpers::into_result;
use crate::notification::NotificationData; use crate::notification::NotificationData;
use crate::notification::NotificationData::IncorrectAnkiFile; use crate::notification::NotificationData::IncorrectAnkiFile;
use log::info; use log::info;
use rusqlite::Connection; use rusqlite::Connection;
use serde_json::Value; use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone)] #[derive(Clone)]
pub struct ImportData(pub(crate) Vec<ImportGroup>); pub struct ImportData(pub(crate) Vec<ImportGroup>);
+6 -2
View File
@@ -67,7 +67,11 @@ impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q
let time = Instant::now(); let time = Instant::now();
let index: u32 = connection 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| { .map_err(|e| {
error!("{}", e); error!("{}", e);
DbSaveError DbSaveError
@@ -104,7 +108,7 @@ impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q
.prepare(Q::load()) .prepare(Q::load())
.expect("Failed to prepare state query"); .expect("Failed to prepare state query");
let iter = stmt let iter = stmt
.query_map([], |row| Ok(Q::from_sql(row)?)) .query_map([], |row| Q::from_sql(row))
.map_err(|e| { .map_err(|e| {
error!("{}", e); error!("{}", e);
DbSaveError DbSaveError
+4 -3
View File
@@ -1,7 +1,7 @@
use crate::fs::*; use crate::fs::*;
use log::error;
use rusqlite::Connection; use rusqlite::Connection;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use log::error;
pub fn create_db() { pub fn create_db() {
let db_file = db_path(); let db_file = db_path();
@@ -130,6 +130,7 @@ impl SqliteConnection {
} }
impl Drop for SqliteConnection { impl Drop for SqliteConnection {
fn drop(&mut self) {
fn drop(&mut self) { self.finish(); } self.finish();
}
} }
+2 -171
View File
@@ -1,9 +1,7 @@
use crate::data::entities::{WordData, WordGroup}; use crate::data::entities::WordData;
use crate::data::repositories::{DataRepository, DataRepositoryInner}; use crate::data::repositories::{DataRepository, DataRepositoryInner};
use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
use crate::notification::NotificationData::{DbLoadError, DbSaveError}; use rusqlite::{Error, Params, Row};
use crate::notification::{MaybeError, NotificationData};
use rusqlite::{Connection, Error, Params, Row, params};
pub type WordRepository = DataRepository<WordData, WordDataLayer>; pub type WordRepository = DataRepository<WordData, WordDataLayer>;
pub type WordRepositoryInner = DataRepositoryInner<WordData, WordDataLayer>; pub type WordRepositoryInner = DataRepositoryInner<WordData, WordDataLayer>;
@@ -59,170 +57,3 @@ impl TableAccessQuerySet<WordData> for WordQuerySet {
) )
} }
} }
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(())
}
-8
View File
@@ -28,14 +28,6 @@ pub fn db_meta_path() -> PathBuf {
app_data_dir().join("data.meta") 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> { pub fn voice_dir() -> Result<PathBuf, NotificationData> {
let directory = app_data_dir().join("voice"); let directory = app_data_dir().join("voice");
if !directory.exists() { if !directory.exists() {
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::app::{fill_state, AppState}; use crate::app::{AppState, fill_state};
use crate::data_provider::sqlite::{create_db}; use crate::data_provider::sqlite::create_db;
use crate::data_provider::web_api::{ use crate::data_provider::web_api::{
get_local_version, get_web_version, load_data, set_local_version, get_local_version, get_web_version, load_data, set_local_version,
}; };
+15 -16
View File
@@ -11,9 +11,9 @@ use crate::pages::dictionary::DictionaryMessage::*;
use crate::pages::dictionary_quiz::DictionaryQuizState; use crate::pages::dictionary_quiz::DictionaryQuizState;
use crate::pages::import::ImportState; use crate::pages::import::ImportState;
use crate::pages::word::WordState; use crate::pages::word::WordState;
use crate::push_error;
use crate::push_error_task; use crate::push_error_task;
use crate::styling::*; use crate::styling::*;
use crate::{jl_button, push_error};
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, TimeDelta, Utc};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use iced::alignment::Vertical::Center; use iced::alignment::Vertical::Center;
@@ -81,7 +81,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
let mut words = vec![]; let mut words = vec![];
let dict = &self.state.dictionary.read(); let dict = &self.state.dictionary.read();
let mut query = dict.query(Arc::new(|_| true)); let query = dict.query(Arc::new(|_| true));
let handles = query.handles(); let handles = query.handles();
words = handles.filter(|h| self.included.contains(h)).collect(); words = handles.filter(|h| self.included.contains(h)).collect();
@@ -257,7 +257,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
let mut dictionary = self.state.dictionary.write(); let mut dictionary = self.state.dictionary.write();
let reload_query = handle.real() == false; let reload_query = !handle.real();
let _ = dictionary.save(&mut handle); let _ = dictionary.save(&mut handle);
if reload_query { if reload_query {
self.displayed_words_query.load(&dictionary); self.displayed_words_query.load(&dictionary);
@@ -373,7 +373,7 @@ impl DictionaryState {
key: word.key.clone().into(), key: word.key.clone().into(),
value: word.value.clone().into(), value: word.value.clone().into(),
tags: word.tags.clone().into(), tags: word.tags.clone().into(),
handle: word_handle.clone(), handle: *word_handle,
}; };
let lazy_line = lazy(word_line_data, Self::word_line); let lazy_line = lazy(word_line_data, Self::word_line);
@@ -387,7 +387,7 @@ impl DictionaryState {
fn word_line<'a>(data: &WordLineState) -> Row<'a, DictionaryMessage> { fn word_line<'a>(data: &WordLineState) -> Row<'a, DictionaryMessage> {
let mut line = Row::new().width(Fill).align_y(Center); let mut line = Row::new().width(Fill).align_y(Center);
let handle = data.handle.clone(); let handle = data.handle;
line = line.push( line = line.push(
checkbox(data.is_included) checkbox(data.is_included)
.label("") .label("")
@@ -434,15 +434,13 @@ impl DictionaryState {
if !data.handle.real() { if !data.handle.real() {
return button("-") return button("-")
.on_press(WordAction(data.handle)) .on_press(WordAction(data.handle))
.style( .style(|_x: &Theme, _status: button::Status| Style {
|_x: &iced::Theme, _status: iced::widget::button::Status| Style {
background: None, background: None,
text_color: Color::BLACK, text_color: Color::BLACK,
border: Border::default(), border: Border::default(),
shadow: Shadow::default(), shadow: Shadow::default(),
snap: false, snap: false,
}, })
)
.width(15); .width(15);
} }
@@ -512,14 +510,15 @@ impl DictionaryState {
let dict = &self.state.dictionary.write(); let dict = &self.state.dictionary.write();
let mut words_query = dict.full_query(); let mut words_query = dict.full_query();
let handles = words_query.handles().collect::<Vec<_>>(); let handles = words_query.handles().collect::<Vec<_>>();
words_query.iter(dict).zip(handles).for_each(|(element, handle)| { words_query
.iter(dict)
.zip(handles)
.for_each(|(element, handle)| {
let tags = split_with_coma(element.tags.as_str()); let tags = split_with_coma(element.tags.as_str());
tags.iter() tags.iter().for_each(|tag| {
.for_each(|tag| {
tags_list.insert(tag.clone()); tags_list.insert(tag.clone());
}); });
self.words_tags.push((handle, tags)) self.words_tags.push((handle, tags))
}); });
let current_tags = self let current_tags = self
@@ -590,12 +589,12 @@ impl DictionaryState {
} }
let groups = self.state.word_groups.read(); let groups = self.state.word_groups.read();
let group = groups.get_static(self.selected_group.clone()).unwrap(); let group = groups.get_static(self.selected_group).unwrap();
iced::widget::column![ iced::widget::column![
scrollable(row).width(Fill).horizontal(), scrollable(row).width(Fill).horizontal(),
row![ row![
button("⇳").on_press(ChangeDirection).style(jl_button), jl_button!("⇳").on_press(ChangeDirection),
text_input("Название группы слов", &group.name) text_input("Название группы слов", &group.name)
.on_input(EditGroup) .on_input(EditGroup)
.width(250) .width(250)
@@ -617,7 +616,7 @@ impl DictionaryState {
} }
fn add_word_button(&self) -> iced::Element<'_, DictionaryMessage> { fn add_word_button(&self) -> iced::Element<'_, DictionaryMessage> {
let button = button("Добавить слово").style(jl_button); let button = jl_button!("Добавить слово");
if self.selected_group.real() { if self.selected_group.real() {
return button.on_press(NewWord).into(); return button.on_press(NewWord).into();
+16 -15
View File
@@ -1,4 +1,7 @@
use crate::app::AppState;
use crate::data::entities::{EntityHandle, WordData}; use crate::data::entities::{EntityHandle, WordData};
use crate::data::repositories::Repository;
use crate::jl_button;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::RootMessage; use crate::navigation::RootMessage;
use crate::navigation::*; use crate::navigation::*;
@@ -6,14 +9,12 @@ use crate::pages::dictionary::split_with_coma;
use crate::pages::dictionary_quiz::DictionaryQuizMessage::*; use crate::pages::dictionary_quiz::DictionaryQuizMessage::*;
use crate::pages::quiz::Score; use crate::pages::quiz::Score;
use crate::styling::*; use crate::styling::*;
use iced::Background::Color;
use iced::border::Radius; use iced::border::Radius;
use iced::widget::container::Style; use iced::widget::container::Style;
use iced::widget::{Row, button, container, row, space, text, text_input}; use iced::widget::{container, row, space, text, text_input, Row};
use iced::{Border, Element, Fill, Task, Theme, alignment}; use iced::Background::Color;
use iced::{alignment, Border, Element, Fill, Task, Theme};
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use crate::app::AppState;
use crate::data::repositories::Repository;
pub struct DictionaryQuizState { pub struct DictionaryQuizState {
state: AppState, state: AppState,
@@ -90,10 +91,7 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
.size(25), .size(25),
] ]
.spacing(DEFAULT_SPACING), .spacing(DEFAULT_SPACING),
row![ row![jl_button!("Закончить").on_press(Back), self.appeal_button()]
button("Закончить").style(jl_button).on_press(Back),
self.appeal_button()
]
.spacing(DEFAULT_SPACING), .spacing(DEFAULT_SPACING),
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
@@ -106,8 +104,13 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
} }
impl DictionaryQuizState { impl DictionaryQuizState {
pub fn new(words: Vec<EntityHandle<WordData>>, reverse: bool, no_typing: bool, state: AppState) -> DictionaryQuizState { pub fn new(
let state = DictionaryQuizState { words: Vec<EntityHandle<WordData>>,
reverse: bool,
no_typing: bool,
state: AppState,
) -> DictionaryQuizState {
Self {
state, state,
words, words,
current_set: Vec::new(), current_set: Vec::new(),
@@ -119,9 +122,7 @@ impl DictionaryQuizState {
reverse, reverse,
laps: 0, laps: 0,
no_typing, no_typing,
}; }
state
} }
fn submit(&mut self) { fn submit(&mut self) {
@@ -218,7 +219,7 @@ impl DictionaryQuizState {
fn appeal_button(&self) -> Element<'_, DictionaryQuizMessage> { fn appeal_button(&self) -> Element<'_, DictionaryQuizMessage> {
if self.is_help && !self.no_typing { if self.is_help && !self.no_typing {
return button("Апелляция").style(jl_button).on_press(Appeal).into(); return jl_button!("Апелляция").on_press(Appeal).into();
} }
space().into() space().into()
} }
+10 -17
View File
@@ -3,18 +3,16 @@ use crate::data::entities::{HistoryNote, WordData};
use crate::data::repositories::Repository; use crate::data::repositories::Repository;
use crate::navigation::*; use crate::navigation::*;
use crate::styling::*; use crate::styling::*;
use HistoryMessage::Back;
use chrono::Local; use chrono::Local;
use iced::alignment::Horizontal::Center; use iced::alignment::Horizontal::Center;
use iced::widget::space::horizontal; use iced::widget::space::horizontal;
use iced::widget::*; use iced::widget::*;
use iced::{Element, Fill, FillPortion, Task}; use iced::{Element, Fill, FillPortion, Task};
use std::sync::Arc; use std::sync::Arc;
use HistoryMessage::Back;
#[derive(Clone)]
pub struct HistoryState { pub struct HistoryState {
list: Vec<(WordData, HistoryNote)>, list: Vec<(WordData, HistoryNote)>,
pub state: AppState,
} }
impl NavigatedPage<HistoryMessage> for HistoryState { impl NavigatedPage<HistoryMessage> for HistoryState {
@@ -50,12 +48,11 @@ impl HistoryState {
let dict = &state.dictionary.read(); let dict = &state.dictionary.read();
let history_repo = &mut state.history.write(); let history_repo = &mut state.history.write();
history_repo.init().expect("TODO: panic message"); history_repo.init().expect("TODO: panic message");
let id = id.clone();
let mut history = history_repo.query(Arc::new(move |h| h.deck_id == id)); let mut history = history_repo.query(Arc::new(move |h| h.deck_id == id));
let words = history let words = history
.iter(history_repo) .iter(history_repo)
.map( .map(|item| {
|item| {let handle = dict.handle_by_id(item.word_id); let handle = dict.handle_by_id(item.word_id);
let word = dict.get_static(handle).unwrap_or(WordData { let word = dict.get_static(handle).unwrap_or(WordData {
id: Default::default(), id: Default::default(),
group_id: Default::default(), group_id: Default::default(),
@@ -65,36 +62,32 @@ impl HistoryState {
additional: Default::default(), additional: Default::default(),
}); });
(word, item.clone()) (word, item.clone())
} })
)
.collect(); .collect();
Self { Self {
list: words, list: words,
state: state.clone(),
} }
} }
fn history_lines(&self) -> Column<'_, HistoryMessage> { fn history_lines(&self) -> Column<'_, HistoryMessage> {
let mut column = Column::new(); let mut column = Column::new();
for (index, item) in &self.list { for (index, item) in &self.list {
let activity = item; let activity = item;
column = column.push( column = column.push(
row![ row![
iced::widget::column![ iced::widget::column![
text!("{}", index.key) text!("{}", index.key).width(Fill).align_x(Center),
.width(Fill) text!("{}", index.value).width(Fill).align_x(Center),
.align_x(Center),
text!("{}", index.value)
.width(Fill)
.align_x(Center),
] ]
.align_x(Center), .align_x(Center),
iced::widget::column![ iced::widget::column![
text!("{} ➞ {}", activity.before, activity.after), text!("{} ➞ {}", activity.before, activity.after),
text!( text!(
"{}", "{}",
activity.timestamp.with_timezone(&Local).format("%d.%m %H:%M") activity
.timestamp
.with_timezone(&Local)
.format("%d.%m %H:%M")
) )
] ]
.spacing(5) .spacing(5)
+5 -5
View File
@@ -1,9 +1,10 @@
use crate::app::AppState; use crate::app::AppState;
use crate::data::entities::EntityData;
use crate::data::entities::WordData; use crate::data::entities::WordData;
use crate::data::entities::WordGroup; 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::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups};
use crate::fs::*; use crate::fs::*;
use crate::jl_button;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::NotificationData::OpenFileError; use crate::notification::NotificationData::OpenFileError;
use crate::notification::{MaybeError, NotificationData}; use crate::notification::{MaybeError, NotificationData};
@@ -30,11 +31,9 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use zip::ZipArchive; use zip::ZipArchive;
use crate::data::repositories::{BatchedRepository, Repository};
const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"]; const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];
#[derive(Clone)]
pub struct ImportState { pub struct ImportState {
state: AppState, state: AppState,
path: Option<PathBuf>, path: Option<PathBuf>,
@@ -155,7 +154,7 @@ impl NavigatedPage<ImportMessage> for ImportState {
scrollable( scrollable(
column![ column![
row![ row![
button("Выбрать файл").style(jl_button).on_press(SelectFile), jl_button!("Выбрать файл").on_press(SelectFile),
text!("{}", { text!("{}", {
if let Some(path) = &self.path { if let Some(path) = &self.path {
path.to_string_lossy().to_string() path.to_string_lossy().to_string()
@@ -435,7 +434,8 @@ impl ImportState {
async move { async move {
spawn_blocking(move || { spawn_blocking(move || {
let temp_file_path = temp_import_path(); 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 import_list = get_words_of_group(&connection, group.id)?;
let mut words_list = Vec::with_capacity(import_list.len()); let mut words_list = Vec::with_capacity(import_list.len());
+2 -2
View File
@@ -1,5 +1,6 @@
use crate::app::USER_FONT; use crate::app::USER_FONT;
use crate::core::kana_set::KanaSet; use crate::core::kana_set::KanaSet;
use crate::jl_button;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::pages::quiz::QuizMessage::*; use crate::pages::quiz::QuizMessage::*;
@@ -8,7 +9,6 @@ use iced::widget::*;
use iced::{Element, Fill, Task, alignment}; use iced::{Element, Fill, Task, alignment};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
#[derive(Clone, Debug)]
pub struct QuizState { pub struct QuizState {
kana: String, kana: String,
current_roman: String, current_roman: String,
@@ -82,7 +82,7 @@ impl NavigatedPage<QuizMessage> for QuizState {
.width(150) .width(150)
.on_input(ContentChanged), .on_input(ContentChanged),
score_display(&self.score), score_display(&self.score),
button("Закончить").style(jl_button).on_press(Back), jl_button!("Закончить").on_press(Back),
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center), .align_x(alignment::Horizontal::Center),
+4 -4
View File
@@ -1,12 +1,12 @@
use crate::jl_button;
use crate::navigation::RootMessage; use crate::navigation::RootMessage;
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page};
use crate::pages::randomizer::RandomizerMessage::{Back, Edit, Start}; use crate::pages::randomizer::RandomizerMessage::{Back, Edit, Start};
use crate::styling::{HALF_SPACING, back_overlay, jl_button}; use crate::styling::{HALF_SPACING, back_overlay};
use iced::Task; use iced::Task;
use iced::widget::{button, text_editor}; use iced::widget::text_editor;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
#[derive(Clone, Debug)]
pub struct RandomizerState { pub struct RandomizerState {
text: text_editor::Content, text: text_editor::Content,
list: Vec<String>, list: Vec<String>,
@@ -56,7 +56,7 @@ impl NavigatedPage<RandomizerMessage> for RandomizerState {
.width(400) .width(400)
.height(400) .height(400)
.placeholder("Каждый элемент с новой строки"), .placeholder("Каждый элемент с новой строки"),
button("Перемешать").style(jl_button).on_press(Start), jl_button!("Перемешать").on_press(Start),
] ]
.spacing(HALF_SPACING) .spacing(HALF_SPACING)
.into(), .into(),
+2 -2
View File
@@ -5,6 +5,7 @@ use crate::data::Id;
use crate::data::entities::{CardState, WordData}; use crate::data::entities::{CardState, WordData};
use crate::data::repositories::Repository; use crate::data::repositories::Repository;
use crate::data_provider::voice::get_voice; use crate::data_provider::voice::get_voice;
use crate::jl_button;
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};
@@ -255,8 +256,7 @@ impl RepetitionState {
} }
fn draw_voice(&self) -> Element<'_, RepetitionMessage> { fn draw_voice(&self) -> Element<'_, RepetitionMessage> {
button("Воспроизвести") jl_button!("Воспроизвести")
.style(jl_button)
.on_press(RepetitionMessage::Play) .on_press(RepetitionMessage::Play)
.into() .into()
} }
+8 -6
View File
@@ -2,6 +2,7 @@ use crate::app::AppState;
use crate::core::deck::DeckData; use crate::core::deck::DeckData;
use crate::data::entities::{DeckSettings, EntityHandle}; use crate::data::entities::{DeckSettings, EntityHandle};
use crate::data::repositories::{BatchedRepository, Repository}; use crate::data::repositories::{BatchedRepository, Repository};
use crate::jl_button;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::MaybeError; use crate::notification::MaybeError;
@@ -122,7 +123,10 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
} }
let mut states = self.state.card_states.write(); let mut states = self.state.card_states.write();
let deck_id = self.settings_handle.id; 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()) { if let Err(e) = states.delete_many(query.as_mut_slice()) {
return push_error_task!(e); return push_error_task!(e);
} }
@@ -163,9 +167,7 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
primary primary
}) })
.on_input(SetFilter), .on_input(SetFilter),
button("Проверить фильтр") jl_button!("Проверить фильтр").on_press(TryFilter),
.style(jl_button)
.on_press(TryFilter),
self.count_view(), self.count_view(),
] ]
.spacing(QUARTER_SPACING), .spacing(QUARTER_SPACING),
@@ -180,7 +182,7 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
) )
.height(Fill), .height(Fill),
row![ row![
button("Сохранить").style(jl_button).on_press(Save), jl_button!("Сохранить").on_press(Save),
horizontal(), horizontal(),
button(if self.real_delete_states { button(if self.real_delete_states {
"Вы уверены?" "Вы уверены?"
@@ -211,7 +213,7 @@ fn update_deck_stats(deck: &DeckSettings, state: &AppState) -> MaybeError {
let deck_id = deck.id; let deck_id = deck.id;
let mut db_stats = states_repo let mut db_stats = states_repo
.query_par(Arc::new(move |s| s.deck_id == deck_id)) .query_par(Arc::new(move |s| s.deck_id == deck_id))
.iter(&states_repo) .iter(states_repo)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let filtered_words = DeckData::list(deck, state) let filtered_words = DeckData::list(deck, state)
.iter() .iter()
+15 -23
View File
@@ -3,6 +3,7 @@ use crate::core::deck::DeckData;
use crate::core::srs::{AppendMode, OrderMode}; use crate::core::srs::{AppendMode, OrderMode};
use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData}; use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData};
use crate::data::repositories::{BatchedRepository, Repository}; use crate::data::repositories::{BatchedRepository, Repository};
use crate::jl_button;
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings}; use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::MaybeError; use crate::notification::MaybeError;
@@ -15,22 +16,21 @@ use crate::push_error_task;
use crate::styling::*; use crate::styling::*;
use chrono::{Days, Local, Utc}; use chrono::{Days, Local, Utc};
use hashbrown::HashSet; use hashbrown::HashSet;
use iced::widget::button::{danger, Status};
pub use iced::widget::button::{Catalog, Style}; pub use iced::widget::button::{Catalog, Style};
use iced::widget::button::{Status, danger};
use iced::widget::tooltip::Position::Top; use iced::widget::tooltip::Position::Top;
use iced::widget::{ use iced::widget::{
Column, Row, button, column, container, lazy, radio, row, scrollable, space, svg, text, button, column, container, lazy, radio, row, scrollable, space, svg, text, text_input, tooltip,
text_input, tooltip, Column, Row,
}; };
use iced::{Background, Border, Center, Color, Element, Fill, Length, Shadow, Task, Theme}; use iced::{Background, Border, Center, Color, Element, Fill, Length, Shadow, Task, Theme};
use iced_core::Padding;
use iced_core::border::Radius; use iced_core::border::Radius;
use iced_core::svg::Handle; use iced_core::svg::Handle;
use iced_core::Padding;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
selected_deck_handle: Option<EntityHandle<DeckSettings>>, selected_deck_handle: Option<EntityHandle<DeckSettings>>,
decks: BTreeMap<EntityHandle<DeckSettings>, DeckRunData>, decks: BTreeMap<EntityHandle<DeckSettings>, DeckRunData>,
@@ -94,7 +94,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
self.clear_selection(); self.clear_selection();
} }
} else { } else {
let _ = self.fill_deck_word_indices(); self.fill_deck_word_indices();
} }
} }
fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> { fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
@@ -126,7 +126,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
DeleteSet => { DeleteSet => {
let index = self.selected_deck_handle.unwrap(); let index = self.selected_deck_handle.unwrap();
self.clear_selection(); self.clear_selection();
let deck = self.decks.remove(&index); self.decks.remove(&index);
if index.real() { if index.real() {
let mut repo = self.state.decks.write(); let mut repo = self.state.decks.write();
@@ -152,7 +152,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
let mut repo = self.state.decks.write(); let mut repo = self.state.decks.write();
repo.set(handle, deck.general_settings.clone()); repo.set(handle, deck.general_settings.clone());
let old = handle.clone(); let old = *handle;
if let Err(err) = repo.save(handle) { if let Err(err) = repo.save(handle) {
return push_error_task!(err); return push_error_task!(err);
} }
@@ -175,7 +175,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
} }
TryFilter => { TryFilter => {
let deck = self.selected_deck().unwrap(); 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(); let deck = self.selected_deck_mut().unwrap();
deck.count = Some(count); deck.count = Some(count);
} }
@@ -227,10 +227,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
row![ row![
column![ column![
scrollable(self.decks_list()).height(Fill), scrollable(self.decks_list()).height(Fill),
button("Добавить") jl_button!("Добавить").width(Fill).on_press(CreateSet),
.style(jl_button)
.width(Fill)
.on_press(CreateSet),
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
.width(Length::FillPortion(1)), .width(Length::FillPortion(1)),
@@ -256,9 +253,7 @@ impl RepetitionsState {
} }
} }
fn get_decks_from_state( fn get_decks_from_state(state: &AppState) -> BTreeMap<EntityHandle<DeckSettings>, DeckRunData> {
state: &AppState,
) -> BTreeMap<EntityHandle<DeckSettings>, DeckRunData> {
state state
.decks .decks
.read() .read()
@@ -331,9 +326,7 @@ impl RepetitionsState {
column![ column![
text!("Фильтр"), text!("Фильтр"),
text_input("", &deck.filter).on_input(SetFilter), text_input("", &deck.filter).on_input(SetFilter),
button("Проверить фильтр") jl_button!("Проверить фильтр").on_press(TryFilter),
.style(jl_button)
.on_press(TryFilter),
self.count_view(deck), self.count_view(deck),
] ]
.spacing(QUARTER_SPACING), .spacing(QUARTER_SPACING),
@@ -361,7 +354,7 @@ impl RepetitionsState {
Some(deck.open_mode), Some(deck.open_mode),
SetOpenMode SetOpenMode
), ),
button("История").style(jl_button).on_press(GoToHistory), jl_button!("История").on_press(GoToHistory),
] ]
.spacing(HALF_SPACING) .spacing(HALF_SPACING)
} }
@@ -378,7 +371,7 @@ impl RepetitionsState {
) )
.height(Fill), .height(Fill),
row![ row![
button("Сохранить").style(jl_button).on_press(Save), jl_button!("Сохранить").on_press(Save),
self.launch_delete_button(deck) self.launch_delete_button(deck)
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
@@ -520,7 +513,7 @@ impl RepetitionsState {
fn decks_list(&self) -> Column<'_, RepetitionsMessage> { fn decks_list(&self) -> Column<'_, RepetitionsMessage> {
let mut column = Column::new(); let mut column = Column::new();
for (i, (handle, deck)) in self.decks.iter().enumerate() { for (handle, deck) in self.decks.iter() {
column = column.push( column = column.push(
button(text!("{}", &deck.name)) button(text!("{}", &deck.name))
.on_press_with(move || SelectSet(*handle)) .on_press_with(move || SelectSet(*handle))
@@ -566,7 +559,6 @@ impl RepetitionsState {
deck: &DeckSettings, deck: &DeckSettings,
indices: impl Iterator<Item = EntityHandle<WordData>>, indices: impl Iterator<Item = EntityHandle<WordData>>,
) -> MaybeError { ) -> MaybeError {
let words = &state.dictionary;
let stats = &mut indices let stats = &mut indices
.map(|i| CardState { .map(|i| CardState {
id: 0.into(), id: 0.into(),
+3 -6
View File
@@ -1,5 +1,6 @@
use crate::app::AppState; use crate::app::AppState;
use crate::core::kana_set::{KanaSet, KanaType}; use crate::core::kana_set::{KanaSet, KanaType};
use crate::jl_button;
use crate::navigation::Page::*; use crate::navigation::Page::*;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::pages::dictionary::DictionaryState; use crate::pages::dictionary::DictionaryState;
@@ -12,12 +13,10 @@ use crate::pages::writing::WritingState;
use crate::styling::*; use crate::styling::*;
use iced::widget::button::{Status, Style}; use iced::widget::button::{Status, Style};
use iced::widget::*; use iced::widget::*;
use iced::{Element, Task, alignment}; use iced::{alignment, Element, Task};
use iced_core::Alignment::Center; use iced_core::Alignment::Center;
use iced_core::Background; use iced_core::Background;
use iced_core::Color; use iced_core::Color;
use parking_lot::Mutex;
use std::sync::Arc;
pub struct SelectorState { pub struct SelectorState {
pub set: KanaSet, pub set: KanaSet,
@@ -81,9 +80,7 @@ impl NavigatedPage<SelectorMessage> for SelectorState {
container( container(
iced::widget::column![ iced::widget::column![
row![ row![
button("あ ↔ ア") jl_button!("あ ↔ ア").on_press(SelectorMessage::Change),
.on_press(SelectorMessage::Change)
.style(jl_button),
button("Словарь") button("Словарь")
.on_press(SelectorMessage::ToDictionary) .on_press(SelectorMessage::ToDictionary)
.style(Self::nav_style), .style(Self::nav_style),
+7 -14
View File
@@ -2,6 +2,7 @@ use crate::app::AppState;
use crate::data::repositories::Repository; use crate::data::repositories::Repository;
use crate::data_provider::settings::{delete_settings, set_setting}; use crate::data_provider::settings::{delete_settings, set_setting};
use crate::data_provider::web_api::*; use crate::data_provider::web_api::*;
use crate::jl_button;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::{NotificationData, NotificationRequest}; use crate::notification::{NotificationData, NotificationRequest};
@@ -31,7 +32,6 @@ pub enum SyncMessage {
DisableSync, DisableSync,
SwitchAutoSync, SwitchAutoSync,
} }
#[derive(Clone)]
pub struct SyncState { pub struct SyncState {
state: AppState, state: AppState,
auto_fetch: bool, auto_fetch: bool,
@@ -221,14 +221,13 @@ impl SyncState {
.padding(3) .padding(3)
) )
.style(rounded_box), .style(rounded_box),
button("Скопировать в буфер обмена") jl_button!("Скопировать в буфер обмена")
.style(jl_button)
.on_press(CopyKey) .on_press(CopyKey)
.width(Fill), .width(Fill),
row![ row![
button("↑ Отправить").style(jl_button).on_press(Send), jl_button!("↑ Отправить").on_press(Send),
space().width(Fill), space().width(Fill),
button("↓ Скачать").style(jl_button).on_press(GetLast) jl_button!("↓ Скачать").on_press(GetLast)
] ]
.spacing(DEFAULT_SPACING), .spacing(DEFAULT_SPACING),
toggler(self.auto_fetch) toggler(self.auto_fetch)
@@ -241,12 +240,8 @@ impl SyncState {
] ]
} else { } else {
column![ column![
button("Создать сохранение") jl_button!("Создать сохранение").on_press(InitSync),
.style(jl_button) jl_button!("Вставить ключ из буфера").on_press(GetKey),
.on_press(InitSync),
button("Вставить ключ из буфера")
.style(jl_button)
.on_press(GetKey),
] ]
} }
} }
@@ -273,9 +268,7 @@ impl SyncState {
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
error!("Ошибка при checkpoint WAL: {e}"); error!("Ошибка при checkpoint WAL: {e}");
}); });
connection connection.execute("VACUUM;", []).unwrap_or_else(|e| {
.execute("VACUUM;", [])
.unwrap_or_else(|e| {
error!("Ошибка при checkpoint WAL: {e}"); error!("Ошибка при checkpoint WAL: {e}");
0 0
}); });
+2 -11
View File
@@ -1,19 +1,13 @@
use crate::app::AppState; use crate::app::AppState;
use crate::data::entities::{EntityHandle, WordData}; use crate::data::entities::{EntityHandle, WordData};
use crate::data_provider::words::{delete_word, update_word};
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::pages::word::WordMessage::*; use crate::pages::word::WordMessage::*;
use crate::push_error;
use crate::push_error_task;
use crate::styling::*; use crate::styling::*;
use iced::widget::button::danger; 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 iced::{Element, Fill, Task};
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Clone)]
pub struct WordState { pub struct WordState {
state: AppState, state: AppState,
handle: EntityHandle<WordData>, handle: EntityHandle<WordData>,
@@ -137,10 +131,7 @@ impl NavigatedPage<WordMessage> for WordState {
impl WordState { impl WordState {
pub fn new(handle: EntityHandle<WordData>, state: AppState) -> Self { pub fn new(handle: EntityHandle<WordData>, state: AppState) -> Self {
Self{ Self { state, handle }
state,
handle,
}
} }
} }
+3 -7
View File
@@ -1,4 +1,5 @@
use crate::core::kana_set::KanaSet; use crate::core::kana_set::KanaSet;
use crate::jl_button;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*; use crate::styling::*;
@@ -6,7 +7,6 @@ use iced::widget::*;
use iced::{Element, Fill, Task, alignment}; use iced::{Element, Fill, Task, alignment};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
#[derive(Clone, Debug)]
pub struct WritingState { pub struct WritingState {
kana: String, kana: String,
set: Vec<(String, String)>, set: Vec<(String, String)>,
@@ -45,12 +45,8 @@ impl NavigatedPage<WritingMessage> for WritingState {
text!("{}", self.kana).size(48), text!("{}", self.kana).size(48),
self.answers(), self.answers(),
row![ row![
button(text!("{}", self.next_text)) jl_button!(text!("{}", self.next_text)).on_press(WritingMessage::Next),
.style(jl_button) jl_button!("Закончить").on_press(WritingMessage::Back),
.on_press(WritingMessage::Next),
button("Закончить")
.style(jl_button)
.on_press(WritingMessage::Back),
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
] ]
+10 -6
View File
@@ -1,7 +1,7 @@
use iced::Element; use iced::Element;
use iced::border::Radius; use iced::border::Radius;
use iced::widget::button::*; use iced::widget::button::*;
use iced::widget::{button, container}; use iced::widget::container;
use iced_core::Length::Fill; use iced_core::Length::Fill;
use iced_core::alignment::Horizontal::Left; use iced_core::alignment::Horizontal::Left;
use iced_core::{Border, Theme}; use iced_core::{Border, Theme};
@@ -14,7 +14,7 @@ pub const BUTTON_RADIUS: f32 = 8.0;
pub const SETTINGS_ICON: &[u8] = include_bytes!("../settings_icon.svg"); pub const SETTINGS_ICON: &[u8] = include_bytes!("../settings_icon.svg");
pub fn jl_button(theme: &Theme, status: Status) -> Style { pub fn jl_button_style(theme: &Theme, status: Status) -> Style {
let mut style = primary(theme, status); let mut style = primary(theme, status);
style.border = Border { style.border = Border {
color: Default::default(), color: Default::default(),
@@ -30,12 +30,16 @@ pub fn cta_button(theme: &Theme, status: Status) -> Style {
style style
} }
#[macro_export]
macro_rules! jl_button {
($content:expr) => {
iced::widget::button($content).style($crate::styling::jl_button_style)
};
}
pub fn back_overlay<'a, T: Clone + 'a>(content: Element<'a, T>, back_message: T) -> Element<'a, T> { pub fn back_overlay<'a, T: Clone + 'a>(content: Element<'a, T>, back_message: T) -> Element<'a, T> {
container( container(
iced::widget::column![ iced::widget::column![jl_button!("Назад").on_press(back_message), content]
button("Назад").style(jl_button).on_press(back_message),
content
]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
.align_x(Left) .align_x(Left)
.width(Fill), .width(Fill),
+4 -4
View File
@@ -73,13 +73,13 @@ mod mod_tests {
#[test] #[test]
fn id_0() { fn id_0() {
let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true); let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true);
assert_eq!(entity.real(), true) assert!(entity.real())
} }
#[test] #[test]
fn id_1() { fn id_1() {
let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false); let entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false);
assert_eq!(entity.real(), false) assert!(!entity.real())
} }
#[test] #[test]
@@ -97,14 +97,14 @@ mod mod_tests {
fn id_5() { fn id_5() {
let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true); let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, true);
entity.set_real(true); entity.set_real(true);
assert_eq!(entity.real(), true) assert!(entity.real())
} }
#[test] #[test]
fn id_6() { fn id_6() {
let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false); let mut entity: EntityHandle<ProtoEntity> = EntityHandle::new(8.into(), 25, false);
entity.set_real(true); entity.set_real(true);
assert_eq!(entity.real(), true) assert!(entity.real())
} }
#[test] #[test]