diff --git a/Cargo.toml b/Cargo.toml index 21a7579..9bb6112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,11 +40,3 @@ panic = "abort" [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } rustc-hash = "2.1.3" - -[[bench]] -name = "map_bench" -harness = false - -[[bench]] -name = "repo_cache_bench" -harness = false diff --git a/benches/map_bench.rs b/benches/map_bench.rs deleted file mode 100644 index dd70daf..0000000 --- a/benches/map_bench.rs +++ /dev/null @@ -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 { - data.iter().map(|(k, v)| (k.clone(), *v)).collect() -} - -fn make_brown_map(data: &[(String, u64)]) -> BrownHashMap { - data.iter().map(|(k, v)| (k.clone(), *v)).collect() -} - -fn make_queries(n: usize, with_misses: bool) -> Vec { - let keys: Vec = (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, 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, 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); diff --git a/benches/repo_cache_bench.rs b/benches/repo_cache_bench.rs deleted file mode 100644 index 8b44a4f..0000000 --- a/benches/repo_cache_bench.rs +++ /dev/null @@ -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, 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, 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, entities: &[Entity]) { - map.clear(); - for entity in entities { - map.insert(entity.id, entity.clone()); - } -} - -fn bench_btreemap_search(map: &BTreeMap, id: i64) -> Option<&Entity> { - map.get(&id) -} - -fn bench_hashmap_insert(map: &mut HashMap, entities: &[Entity]) { - map.clear(); - for entity in entities { - map.insert(entity.id, entity.clone()); - } -} - -fn bench_hashmap_search(map: &HashMap, id: i64) -> Option<&Entity> { - map.get(&id) -} - -fn bench_hashbrown_insert(map: &mut HashbrownMap, entities: &[Entity]) { - map.clear(); - for entity in entities { - map.insert(entity.id, entity.clone()); - } -} - -fn bench_hashbrown_search(map: &HashbrownMap, id: i64) -> Option<&Entity> { - map.get(&id) -} - -fn bench_fxhashmap_insert(map: &mut FxHashMap, entities: &[Entity]) { - map.clear(); - for entity in entities { - map.insert(entity.id, entity.clone()); - } -} - -fn bench_fxhashmap_search(map: &FxHashMap, id: i64) -> Option<&Entity> { - map.get(&id) -} - -// ========== ГЕНЕРАЦИЯ ДАННЫХ ========== - -fn generate_entities(count: usize) -> Vec { - (0..count).map(|i| Entity::new(i as i64)).collect() -} - -fn generate_search_ids(count: usize, max_id: i64) -> Vec { - // 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::>(); - 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); diff --git a/src/core/deck.rs b/src/core/deck.rs index c7e687b..74c4343 100644 --- a/src/core/deck.rs +++ b/src/core/deck.rs @@ -33,7 +33,9 @@ impl DeckData { .map(|card_state| EntityHandle::from_id(card_state.word_id)) .collect(); let states = query.handles().collect::>>(); - let fsrs = Fsrs(FsrsModule::new(Self::set_links(states.as_slice(), &states_repo).as_slice())); + 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, diff --git a/src/core/srs.rs b/src/core/srs.rs index a7a2936..4691e3b 100644 --- a/src/core/srs.rs +++ b/src/core/srs.rs @@ -44,9 +44,7 @@ impl SRSModule for RandomSRSModule { fn new(set: &[&CardState]) -> Self { let mut basket = (0..set.len()).collect::>(); 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::>(); - 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(); diff --git a/src/data/entities.rs b/src/data/entities.rs index 4412064..60a2acc 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -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::>(); + let values = query.iter(self).collect::>(); 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 { } impl Clone for EntityHandle { - fn clone(&self) -> Self { - EntityHandle { - id: self.id, - index: self.index, - _phantom: Default::default(), - } - } + fn clone(&self) -> Self { *self } } + impl Copy for EntityHandle {} impl EntityHandle { pub fn new(id: Id, index: usize, real: bool) -> Self { diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 3dd5990..95553bf 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -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 where @@ -31,13 +31,19 @@ where fn create_now(&mut self, new: T) -> Result, NotificationData>; fn save(&mut self, entity: &mut EntityHandle) -> MaybeError; fn delete(&mut self, handle: EntityHandle) -> MaybeError; - fn find(&self, predicate: F) -> Option> where F : Fn(&T) -> bool; + fn find(&self, predicate: F) -> Option> + 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 where T: 'a; - fn iter_mut<'a>(&'a mut self) -> impl Iterator where T: 'a; + fn iter<'a>(&'a self) -> impl Iterator + where + T: 'a; + fn iter_mut<'a>(&'a mut self) -> impl Iterator + where + T: 'a; } struct QueryManger @@ -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, D: DbLayer> Query { - pub fn iter<'a>(&mut self, data_repository_inner: &'a DataRepositoryInner) -> impl Iterator + pub fn iter<'a>( + &mut self, + data_repository_inner: &'a DataRepositoryInner, + ) -> impl Iterator where T: 'a, { @@ -130,7 +137,10 @@ impl, D: DbLayer> Qu .filter_map(move |e| data_repository_inner.get_ref(e)) } - pub fn iter_with_handles<'a>(&mut self, data_repository_inner: &'a DataRepositoryInner) -> impl Iterator)> + pub fn iter_with_handles<'a>( + &mut self, + data_repository_inner: &'a DataRepositoryInner, + ) -> impl Iterator)> where T: 'a, { @@ -139,9 +149,9 @@ impl, D: DbLayer> 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(&mut self, repo: &mut DataRepositoryInner, mut f: F) where F: FnMut(&mut T), @@ -190,19 +200,26 @@ impl, D: DbLayer> Qu .collect::>(), ); - 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> { self.cache.iter().cloned() } - + pub fn len(&self) -> usize { self.cache.len() } } -impl, D: DbLayer> Index for Query { +impl, D: DbLayer> Index + for Query +{ type Output = EntityHandle; fn index(&self, index: usize) -> &Self::Output { @@ -210,7 +227,9 @@ impl, D: DbLayer> In } } -impl, D: DbLayer> IndexMut for Query { +impl, D: DbLayer> IndexMut + for Query +{ fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.cache[index] } @@ -221,7 +240,10 @@ where D: BatchedDbLayer, T: EntityData, { - fn create_now_many(&mut self, entities: &mut [T]) -> Result>, NotificationData>; + fn create_now_many( + &mut self, + entities: &mut [T], + ) -> Result>, NotificationData>; fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; } @@ -257,10 +279,10 @@ pub struct DataRepositoryInner> { shifts: usize, } -impl + Default> DataRepository { +impl + Default> DataRepository { pub fn new(connection: Arc>) -> DataRepository { - 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 + Default> DataRepository { } } - impl> Deref for DataRepository { type Target = Arc>>; @@ -283,7 +304,9 @@ impl> Deref for DataRepository { } } -impl> DataRepositoryInner { +impl> + DataRepositoryInner +{ pub fn query_par( &self, rule: Arc bool + Send + Sync>, @@ -306,7 +329,7 @@ impl bool + Send + Sync>, + rule: Arc bool + Send + Sync>, ) -> Query, D> where T: Send + Sync, @@ -323,9 +346,9 @@ impl Query, D> { - Query{ + Query { cache: vec![], rule: Arc::new(|_| false), parallel: false, @@ -335,7 +358,7 @@ impl Query, D> { - Query{ + Query { cache: vec![], rule: Arc::new(|_| false), parallel: true, @@ -433,7 +456,7 @@ impl> Repository for DataRepositoryIn } fn save(&mut self, entity: &mut EntityHandle) -> 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> Repository 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> Repository for DataRepositoryIn fn find(&self, predicate: F) -> Option> 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> Repository for DataRepositoryIn self.shifts } - fn iter<'a>(&'a self) -> impl Iterator + fn iter<'a>(&'a self) -> impl Iterator where - T: 'a + T: 'a, { self.cache.iter().chain(self.temporal.iter()) } - fn iter_mut<'a>(&'a mut self) -> impl Iterator + fn iter_mut<'a>(&'a mut self) -> impl Iterator where - T: 'a + T: 'a, { self.cache.iter_mut().chain(self.temporal.iter_mut()) } @@ -535,10 +557,18 @@ impl> Repository for DataRepositoryIn impl + DbLayer> BatchedRepository for DataRepositoryInner { - fn create_now_many(&mut self, entities: &mut [T]) -> Result>, NotificationData> { - self.data_layer.add_many_entities(entities, &mut self.connection.lock())?; + fn create_now_many( + &mut self, + entities: &mut [T], + ) -> Result>, 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 + DbLayer> 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)) diff --git a/src/data_provider/card_states.rs b/src/data_provider/card_states.rs index 266e0ab..ef97ecd 100644 --- a/src/data_provider/card_states.rs +++ b/src/data_provider/card_states.rs @@ -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; -pub type CardStateRepositoryInner = - DataRepositoryInner; +pub type CardStateRepository = DataRepository; +pub type CardStateRepositoryInner = DataRepositoryInner; pub type CardStateDbLayer = SqliteDbLayer; #[derive(Clone)] pub struct CardStatesQuerySet; @@ -73,35 +67,6 @@ impl TableAccessQuerySet for CardStatesQuerySet { ) } } -pub fn load_stats_of_deck( - set: &DeckSettings, - connection: &Connection, -) -> Result, 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::(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::(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 { if value == 0 { @@ -122,7 +87,10 @@ fn memory_to_i64(value: &Option) -> i64 { } impl CardStateRepositoryInner { - pub fn deck_states(&self, deck_id: Id) -> Query { - self.query(Arc::new(move |state| {state.deck_id == deck_id})) + pub fn deck_states( + &self, + deck_id: Id, + ) -> Query { + self.query(Arc::new(move |state| state.deck_id == deck_id)) } -} \ No newline at end of file +} diff --git a/src/data_provider/custom_fields.rs b/src/data_provider/custom_fields.rs index 6f09efa..feb81db 100644 --- a/src/data_provider/custom_fields.rs +++ b/src/data_provider/custom_fields.rs @@ -3,7 +3,8 @@ use crate::data::repositories::DataRepository; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; use rusqlite::{Error, Params, Row}; -pub type CustomFieldRepository = DataRepository>; +pub type CustomFieldRepository = + DataRepository>; pub struct CustomFieldsQuerySet; impl TableAccessQuerySet for CustomFieldsQuerySet { diff --git a/src/data_provider/decks.rs b/src/data_provider/decks.rs index d647467..e7fe7f9 100644 --- a/src/data_provider/decks.rs +++ b/src/data_provider/decks.rs @@ -52,4 +52,4 @@ impl TableAccessQuerySet for DeckQuerySet { entity.id, ) } -} \ No newline at end of file +} diff --git a/src/data_provider/history.rs b/src/data_provider/history.rs index 17007b1..7b86513 100644 --- a/src/data_provider/history.rs +++ b/src/data_provider/history.rs @@ -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, })) } -} \ No newline at end of file +} diff --git a/src/data_provider/import.rs b/src/data_provider/import.rs index ebdbaaa..bf59d9b 100644 --- a/src/data_provider/import.rs +++ b/src/data_provider/import.rs @@ -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); diff --git a/src/data_provider/mod.rs b/src/data_provider/mod.rs index 31e0e22..0979afc 100644 --- a/src/data_provider/mod.rs +++ b/src/data_provider/mod.rs @@ -67,7 +67,11 @@ impl> DbLayer for SqliteDbLayer> DbLayer for SqliteDbLayer; pub type WordRepositoryInner = DataRepositoryInner; @@ -58,171 +56,4 @@ impl TableAccessQuerySet 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, 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, 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(()) -} +} \ No newline at end of file diff --git a/src/fs.rs b/src/fs.rs index 5ef1e37..f6e5e8e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -28,14 +28,6 @@ pub fn db_meta_path() -> PathBuf { app_data_dir().join("data.meta") } -pub fn history_dir() -> Result { - 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 { let directory = app_data_dir().join("voice"); if !directory.exists() { diff --git a/src/navigation.rs b/src/navigation.rs index e205793..be23c81 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -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, }; diff --git a/src/pages/dictionary.rs b/src/pages/dictionary.rs index ea699db..3470630 100644 --- a/src/pages/dictionary.rs +++ b/src/pages/dictionary.rs @@ -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::>(); - 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 diff --git a/src/pages/dictionary_quiz.rs b/src/pages/dictionary_quiz.rs index 865004a..129dc98 100644 --- a/src/pages/dictionary_quiz.rs +++ b/src/pages/dictionary_quiz.rs @@ -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 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 for DictionaryQuizState { } impl DictionaryQuizState { - pub fn new(words: Vec>, reverse: bool, no_typing: bool, state: AppState) -> DictionaryQuizState { - let state = DictionaryQuizState { + pub fn new( + words: Vec>, + 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) { diff --git a/src/pages/history.rs b/src/pages/history.rs index 7665efe..b3a730c 100644 --- a/src/pages/history.rs +++ b/src/pages/history.rs @@ -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 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)); } diff --git a/src/pages/import.rs b/src/pages/import.rs index b5dd9f0..7d72651 100644 --- a/src/pages/import.rs +++ b/src/pages/import.rs @@ -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()); diff --git a/src/pages/quiz.rs b/src/pages/quiz.rs index 46f606e..b50c361 100644 --- a/src/pages/quiz.rs +++ b/src/pages/quiz.rs @@ -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 { diff --git a/src/pages/randomizer.rs b/src/pages/randomizer.rs index 19b5908..c0dcff6 100644 --- a/src/pages/randomizer.rs +++ b/src/pages/randomizer.rs @@ -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; diff --git a/src/pages/repetition.rs b/src/pages/repetition.rs index 6542d48..fea90c2 100644 --- a/src/pages/repetition.rs +++ b/src/pages/repetition.rs @@ -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; diff --git a/src/pages/repetition_settings.rs b/src/pages/repetition_settings.rs index 8770aeb..c86af10 100644 --- a/src/pages/repetition_settings.rs +++ b/src/pages/repetition_settings.rs @@ -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 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::>(); + let mut query = states + .query(Arc::new(move |state| state.deck_id == deck_id)) + .handles() + .collect::>(); if let Err(e) = states.delete_many(query.as_mut_slice()) { return push_error_task!(e); } @@ -164,8 +167,7 @@ impl NavigatedPage 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::>(); let filtered_words = DeckData::list(deck, state) .iter() diff --git a/src/pages/repetitions.rs b/src/pages/repetitions.rs index 57ba9e3..27e2acd 100644 --- a/src/pages/repetitions.rs +++ b/src/pages/repetitions.rs @@ -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 for RepetitionsState { self.clear_selection(); } } else { - let _ = self.fill_deck_word_indices(); + self.fill_deck_word_indices(); } } fn update(&mut self, message: RepetitionsMessage) -> Task { @@ -127,7 +127,7 @@ impl NavigatedPage 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 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 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 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, DeckRunData> { + fn get_decks_from_state(state: &AppState) -> BTreeMap, 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>, ) -> MaybeError { - let words = &state.dictionary; let stats = &mut indices .map(|i| CardState { id: 0.into(), diff --git a/src/pages/selector.rs b/src/pages/selector.rs index f4b893e..825a16f 100644 --- a/src/pages/selector.rs +++ b/src/pages/selector.rs @@ -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 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), diff --git a/src/pages/sync.rs b/src/pages/sync.rs index 31317b1..db92f77 100644 --- a/src/pages/sync.rs +++ b/src/pages/sync.rs @@ -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| { diff --git a/src/pages/word.rs b/src/pages/word.rs index bb57c89..aafc7b5 100644 --- a/src/pages/word.rs +++ b/src/pages/word.rs @@ -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 for WordState { impl WordState { pub fn new(handle: EntityHandle, state: AppState) -> Self { - Self{ - state, - handle, - } + Self { state, handle } } } diff --git a/src/pages/writing.rs b/src/pages/writing.rs index f156553..20db3f5 100644 --- a/src/pages/writing.rs +++ b/src/pages/writing.rs @@ -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 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) ] diff --git a/src/styling.rs b/src/styling.rs index 8406b0c..852d4bf 100644 --- a/src/styling.rs +++ b/src/styling.rs @@ -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) diff --git a/src/tests.rs b/src/tests.rs index c2e777a..584b91e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -73,13 +73,13 @@ mod mod_tests { #[test] fn id_0() { let entity: EntityHandle = EntityHandle::new(8.into(), 25, true); - assert_eq!(entity.real(), true) + assert!(entity.real()) } #[test] fn id_1() { let entity: EntityHandle = 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 = 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 = EntityHandle::new(8.into(), 25, false); entity.set_real(true); - assert_eq!(entity.real(), true) + assert!(entity.real()) } #[test]