Code cleanup
This commit is contained in:
1 parent
1ad991667b
commit
f66bf69ef5
32 files changed
+234
-899
No files matched your search
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
+7
-8
@@ -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,16 +88,15 @@ 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)
|
||||||
.collect::<Vec<f32>>();
|
.collect::<Vec<f32>>();
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
+7
-13
@@ -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,17 +86,16 @@ 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();
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for note in values {
|
for note in values {
|
||||||
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 {
|
||||||
|
|||||||
+74
-44
@@ -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,9 +149,9 @@ 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)
|
||||||
where
|
where
|
||||||
F: FnMut(&mut T),
|
F: FnMut(&mut T),
|
||||||
@@ -190,19 +200,26 @@ 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>> {
|
||||||
self.cache.iter().cloned()
|
self.cache.iter().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.cache.len()
|
self.cache.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Index<usize> for Query<T, R, D> {
|
impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Index<usize>
|
||||||
|
for Query<T, R, D>
|
||||||
|
{
|
||||||
type Output = EntityHandle<T>;
|
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;
|
||||||
}
|
}
|
||||||
@@ -257,10 +279,10 @@ pub struct DataRepositoryInner<T: Clone + EntityData, D: DbLayer<T>> {
|
|||||||
shifts: usize,
|
shifts: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
||||||
pub fn new(connection: Arc<Mutex<SqliteConnection>>) -> DataRepository<T, D> {
|
pub fn new(connection: Arc<Mutex<SqliteConnection>>) -> DataRepository<T, D> {
|
||||||
Self{
|
Self {
|
||||||
inner: Arc::new(RwLock::new(DataRepositoryInner{
|
inner: Arc::new(RwLock::new(DataRepositoryInner {
|
||||||
cache: vec![],
|
cache: vec![],
|
||||||
temporal: vec![],
|
temporal: vec![],
|
||||||
next_temporal: 0,
|
next_temporal: 0,
|
||||||
@@ -274,7 +296,6 @@ impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<T: Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> {
|
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>,
|
||||||
@@ -306,7 +329,7 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
|||||||
|
|
||||||
pub fn query(
|
pub fn query(
|
||||||
&self,
|
&self,
|
||||||
rule: Arc<dyn Fn(& T) -> bool + Send + Sync>,
|
rule: Arc<dyn Fn(&T) -> bool + Send + Sync>,
|
||||||
) -> Query<T, DataRepositoryInner<T, D>, D>
|
) -> Query<T, DataRepositoryInner<T, D>, D>
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
@@ -323,9 +346,9 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
|||||||
|
|
||||||
query
|
query
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_query(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
pub fn empty_query(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
||||||
Query{
|
Query {
|
||||||
cache: vec![],
|
cache: vec![],
|
||||||
rule: Arc::new(|_| false),
|
rule: Arc::new(|_| false),
|
||||||
parallel: false,
|
parallel: false,
|
||||||
@@ -335,7 +358,7 @@ impl<T: EntityData + Clone + std::marker::Send + std::marker::Sync, D: DbLayer<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_query_par(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
pub fn empty_query_par(&self) -> Query<T, DataRepositoryInner<T, D>, D> {
|
||||||
Query{
|
Query {
|
||||||
cache: vec![],
|
cache: vec![],
|
||||||
rule: Arc::new(|_| false),
|
rule: Arc::new(|_| false),
|
||||||
parallel: true,
|
parallel: true,
|
||||||
@@ -433,7 +456,7 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, entity: &mut EntityHandle<T>) -> MaybeError {
|
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
|
||||||
}
|
}
|
||||||
@@ -518,16 +540,16 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
|
|||||||
self.shifts
|
self.shifts
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ impl TableAccessQuerySet<DeckSettings> for DeckQuerySet {
|
|||||||
entity.id,
|
entity.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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>);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
+3
-172
@@ -1,9 +1,7 @@
|
|||||||
use crate::data::entities::{WordData, WordGroup};
|
use crate::data::entities::WordData;
|
||||||
use crate::data::repositories::{DataRepository, DataRepositoryInner};
|
use crate::data::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>;
|
||||||
@@ -58,171 +56,4 @@ impl TableAccessQuerySet<WordData> for WordQuerySet {
|
|||||||
entity.id,
|
entity.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_word(word: &mut WordData, connection: &Connection) -> MaybeError {
|
|
||||||
let index : u32 = connection
|
|
||||||
.query_row(
|
|
||||||
"INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
||||||
(
|
|
||||||
&word.key,
|
|
||||||
&word.value,
|
|
||||||
&word.tags,
|
|
||||||
serde_json::to_string(&word.additional).unwrap_or_default(),
|
|
||||||
&word.group_id,
|
|
||||||
),
|
|
||||||
|row| row.get(0),
|
|
||||||
).map_err(|_| DbSaveError)?;
|
|
||||||
|
|
||||||
word.id = index.into();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_words(words: &mut [WordData], connection: &mut Connection) -> MaybeError {
|
|
||||||
let tx = connection.transaction().map_err(|_| DbLoadError)?;
|
|
||||||
let count = words.len();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = tx
|
|
||||||
.prepare(
|
|
||||||
"INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
||||||
)
|
|
||||||
.expect("Failed to prepare statement");
|
|
||||||
|
|
||||||
for word in words.iter() {
|
|
||||||
stmt.execute(params![
|
|
||||||
word.key,
|
|
||||||
word.value,
|
|
||||||
word.tags,
|
|
||||||
serde_json::to_string(&word.additional).unwrap_or_default(),
|
|
||||||
word.group_id
|
|
||||||
])
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let last_index: u32 = tx.last_insert_rowid() as u32;
|
|
||||||
tx.commit().map_err(|_| DbSaveError)?;
|
|
||||||
|
|
||||||
let start_index = last_index - (count as u32) + 1;
|
|
||||||
|
|
||||||
for (index, id) in (start_index..=last_index).enumerate() {
|
|
||||||
words[index].id = id.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_word(word: &mut WordData, connection: &Connection) -> MaybeError {
|
|
||||||
if !word.id.is_valid() {
|
|
||||||
Ok(add_word(word, connection)?)
|
|
||||||
} else {
|
|
||||||
connection
|
|
||||||
.execute(
|
|
||||||
"UPDATE words SET key = ?1, value = ?2, tags = ?3, more = ?4 WHERE id = ?5",
|
|
||||||
(
|
|
||||||
&word.key,
|
|
||||||
&word.value,
|
|
||||||
&word.tags,
|
|
||||||
serde_json::to_string(&word.additional).map_err(|_| DbSaveError)?,
|
|
||||||
&word.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_word(word: &WordData, connection: &Connection) -> MaybeError {
|
|
||||||
if !word.id.is_valid() {
|
|
||||||
return Err(DbSaveError);
|
|
||||||
}
|
|
||||||
connection
|
|
||||||
.execute("DELETE FROM words WHERE id = ?1", (&word.id,))
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_words(connection: &Connection) -> Result<Vec<WordData>, NotificationData> {
|
|
||||||
let mut stmt = connection
|
|
||||||
.prepare("SELECT id, key, value, tags, more, group_id FROM words")
|
|
||||||
.expect("Failed to prepare statement");
|
|
||||||
let word_iter = stmt
|
|
||||||
.query_map([], |row| {
|
|
||||||
let additional: String = row.get(4)?;
|
|
||||||
Ok(WordData {
|
|
||||||
id: row.get(0)?,
|
|
||||||
key: row.get(1)?,
|
|
||||||
value: row.get(2)?,
|
|
||||||
tags: row.get(3)?,
|
|
||||||
additional: serde_json::from_str(&additional).unwrap_or_default(),
|
|
||||||
group_id: row.get(5)?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.map_err(|_| DbLoadError)?;
|
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity(1024);
|
|
||||||
word_iter
|
|
||||||
.filter_map(|res| res.ok())
|
|
||||||
.for_each(|i| buffer.push(i));
|
|
||||||
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_word_groups(connection: &Connection) -> Result<Vec<WordGroup>, NotificationData> {
|
|
||||||
let mut stmt = connection
|
|
||||||
.prepare("SELECT id, name FROM word_groups")
|
|
||||||
.expect("Failed to prepare statement");
|
|
||||||
let group_iter = stmt
|
|
||||||
.query_map([], |row| {
|
|
||||||
Ok(WordGroup {
|
|
||||||
id: row.get(0)?,
|
|
||||||
name: row.get(1)?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.map_err(|_| DbLoadError)?;
|
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity(8);
|
|
||||||
group_iter
|
|
||||||
.filter_map(|res| res.ok())
|
|
||||||
.for_each(|i| buffer.push(i));
|
|
||||||
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_group(group: &mut WordGroup, connection: &Connection) -> MaybeError {
|
|
||||||
let index: u32 = connection
|
|
||||||
.query_row(
|
|
||||||
"INSERT INTO word_groups (name) VALUES (?1)",
|
|
||||||
(&group.name,),
|
|
||||||
|row| row.get(0),
|
|
||||||
)
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
|
|
||||||
group.id = index.into();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_group(group: &mut WordGroup, connection: &Connection) -> MaybeError {
|
|
||||||
if !group.id.is_valid() {
|
|
||||||
Ok(add_group(group, connection)?)
|
|
||||||
} else {
|
|
||||||
connection
|
|
||||||
.execute(
|
|
||||||
"UPDATE word_groups SET name = ?1 WHERE id = ?2",
|
|
||||||
(&group.name, &group.id),
|
|
||||||
)
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_group(group: &WordGroup, connection: &Connection) -> MaybeError {
|
|
||||||
if !group.id.is_valid() {
|
|
||||||
return Err(DbSaveError);
|
|
||||||
}
|
|
||||||
connection
|
|
||||||
.execute("DELETE FROM word_groups WHERE id = ?1", (&group.id,))
|
|
||||||
.map_err(|_| DbSaveError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -28,14 +28,6 @@ pub fn db_meta_path() -> PathBuf {
|
|||||||
app_data_dir().join("data.meta")
|
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
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
+16
-17
@@ -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::{jl_button, 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;
|
||||||
@@ -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: &Theme, _status: 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,15 +510,16 @@ 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
|
||||||
let tags = split_with_coma(element.tags.as_str());
|
.iter(dict)
|
||||||
tags.iter()
|
.zip(handles)
|
||||||
.for_each(|tag| {
|
.for_each(|(element, handle)| {
|
||||||
|
let tags = split_with_coma(element.tags.as_str());
|
||||||
|
tags.iter().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
|
||||||
.tag_map
|
.tag_map
|
||||||
|
|||||||
@@ -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,15 +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;
|
|
||||||
use crate::jl_button;
|
|
||||||
|
|
||||||
pub struct DictionaryQuizState {
|
pub struct DictionaryQuizState {
|
||||||
state: AppState,
|
state: AppState,
|
||||||
@@ -91,11 +91,8 @@ 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()]
|
||||||
jl_button!("Закончить").on_press(Back),
|
.spacing(DEFAULT_SPACING),
|
||||||
self.appeal_button()
|
|
||||||
]
|
|
||||||
.spacing(DEFAULT_SPACING),
|
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING)
|
.spacing(DEFAULT_SPACING)
|
||||||
.align_x(alignment::Horizontal::Center),
|
.align_x(alignment::Horizontal::Center),
|
||||||
@@ -107,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(),
|
||||||
@@ -120,9 +122,7 @@ impl DictionaryQuizState {
|
|||||||
reverse,
|
reverse,
|
||||||
laps: 0,
|
laps: 0,
|
||||||
no_typing,
|
no_typing,
|
||||||
};
|
}
|
||||||
|
|
||||||
state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit(&mut self) {
|
fn submit(&mut self) {
|
||||||
|
|||||||
+24
-27
@@ -3,13 +3,13 @@ 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)]
|
#[derive(Clone)]
|
||||||
pub struct HistoryState {
|
pub struct HistoryState {
|
||||||
@@ -37,9 +37,9 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
|
|||||||
.width(FillPortion(5)),
|
.width(FillPortion(5)),
|
||||||
horizontal().width(FillPortion(1))
|
horizontal().width(FillPortion(1))
|
||||||
]
|
]
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.into(),
|
.into(),
|
||||||
Back,
|
Back,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,23 +50,22 @@ 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 id = id;
|
||||||
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(),
|
||||||
key: "Удаленное слово".to_string(),
|
key: "Удаленное слово".to_string(),
|
||||||
value: "".to_string(),
|
value: "".to_string(),
|
||||||
tags: "".to_string(),
|
tags: "".to_string(),
|
||||||
additional: Default::default(),
|
additional: Default::default(),
|
||||||
});
|
});
|
||||||
(word, item.clone())
|
(word, item.clone())
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.collect();
|
.collect();
|
||||||
Self {
|
Self {
|
||||||
list: words,
|
list: words,
|
||||||
@@ -77,30 +76,28 @@ impl HistoryState {
|
|||||||
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)
|
||||||
.align_x(Center)
|
.align_x(Center)
|
||||||
]
|
]
|
||||||
.spacing(5),
|
.spacing(5),
|
||||||
);
|
);
|
||||||
column = column.push(rule::horizontal(1));
|
column = column.push(rule::horizontal(1));
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,15 +1,15 @@
|
|||||||
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};
|
||||||
use crate::pages::import::ImportMessage::*;
|
use crate::pages::import::ImportMessage::*;
|
||||||
use crate::push_error;
|
use crate::push_error;
|
||||||
use crate::jl_button;
|
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use iced::widget::button::danger;
|
use iced::widget::button::danger;
|
||||||
use iced::widget::container::success;
|
use iced::widget::container::success;
|
||||||
@@ -31,7 +31,6 @@ 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"];
|
||||||
|
|
||||||
@@ -436,7 +435,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());
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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::*;
|
||||||
@@ -7,7 +8,6 @@ use crate::styling::*;
|
|||||||
use iced::widget::*;
|
use iced::widget::*;
|
||||||
use iced::{Element, Fill, Task, alignment};
|
use iced::{Element, Fill, Task, alignment};
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use crate::jl_button;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct QuizState {
|
pub struct QuizState {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
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};
|
use crate::styling::{HALF_SPACING, back_overlay};
|
||||||
use crate::jl_button;
|
|
||||||
use iced::Task;
|
use iced::Task;
|
||||||
use iced::widget::text_editor;
|
use iced::widget::text_editor;
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ 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};
|
||||||
use crate::pages::repetitions::DeckRunData;
|
use crate::pages::repetitions::DeckRunData;
|
||||||
use crate::jl_button;
|
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use crate::{push_error, push_error_task};
|
use crate::{push_error, push_error_task};
|
||||||
use iced::alignment::Horizontal::Center;
|
use iced::alignment::Horizontal::Center;
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ 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;
|
||||||
use crate::pages::repetition_settings::RepetitionSettingsMessage::*;
|
use crate::pages::repetition_settings::RepetitionSettingsMessage::*;
|
||||||
use crate::jl_button;
|
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use crate::{push_error, push_error_task};
|
use crate::{push_error, push_error_task};
|
||||||
use iced::widget::button::danger;
|
use iced::widget::button::danger;
|
||||||
@@ -123,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);
|
||||||
}
|
}
|
||||||
@@ -164,8 +167,7 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
|
|||||||
primary
|
primary
|
||||||
})
|
})
|
||||||
.on_input(SetFilter),
|
.on_input(SetFilter),
|
||||||
jl_button!("Проверить фильтр")
|
jl_button!("Проверить фильтр").on_press(TryFilter),
|
||||||
.on_press(TryFilter),
|
|
||||||
self.count_view(),
|
self.count_view(),
|
||||||
]
|
]
|
||||||
.spacing(QUARTER_SPACING),
|
.spacing(QUARTER_SPACING),
|
||||||
@@ -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()
|
||||||
|
|||||||
+13
-19
@@ -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;
|
||||||
@@ -12,21 +13,20 @@ use crate::pages::repetition_settings::RepetitionSettingsState;
|
|||||||
use crate::pages::repetitions::RepetitionsMessage::*;
|
use crate::pages::repetitions::RepetitionsMessage::*;
|
||||||
use crate::push_error;
|
use crate::push_error;
|
||||||
use crate::push_error_task;
|
use crate::push_error_task;
|
||||||
use crate::jl_button;
|
|
||||||
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;
|
||||||
@@ -95,7 +95,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> {
|
||||||
@@ -127,7 +127,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();
|
||||||
@@ -153,7 +153,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);
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,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);
|
||||||
}
|
}
|
||||||
@@ -228,9 +228,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
|||||||
row![
|
row![
|
||||||
column![
|
column![
|
||||||
scrollable(self.decks_list()).height(Fill),
|
scrollable(self.decks_list()).height(Fill),
|
||||||
jl_button!("Добавить")
|
jl_button!("Добавить").width(Fill).on_press(CreateSet),
|
||||||
.width(Fill)
|
|
||||||
.on_press(CreateSet),
|
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING)
|
.spacing(DEFAULT_SPACING)
|
||||||
.width(Length::FillPortion(1)),
|
.width(Length::FillPortion(1)),
|
||||||
@@ -256,9 +254,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,8 +327,7 @@ impl RepetitionsState {
|
|||||||
column![
|
column![
|
||||||
text!("Фильтр"),
|
text!("Фильтр"),
|
||||||
text_input("", &deck.filter).on_input(SetFilter),
|
text_input("", &deck.filter).on_input(SetFilter),
|
||||||
jl_button!("Проверить фильтр")
|
jl_button!("Проверить фильтр").on_press(TryFilter),
|
||||||
.on_press(TryFilter),
|
|
||||||
self.count_view(deck),
|
self.count_view(deck),
|
||||||
]
|
]
|
||||||
.spacing(QUARTER_SPACING),
|
.spacing(QUARTER_SPACING),
|
||||||
@@ -519,7 +514,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))
|
||||||
@@ -565,7 +560,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(),
|
||||||
|
|||||||
@@ -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,13 +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;
|
|
||||||
use crate::jl_button;
|
|
||||||
|
|
||||||
pub struct SelectorState {
|
pub struct SelectorState {
|
||||||
pub set: KanaSet,
|
pub set: KanaSet,
|
||||||
@@ -82,8 +80,7 @@ impl NavigatedPage<SelectorMessage> for SelectorState {
|
|||||||
container(
|
container(
|
||||||
iced::widget::column![
|
iced::widget::column![
|
||||||
row![
|
row![
|
||||||
jl_button!("あ ↔ ア")
|
jl_button!("あ ↔ ア").on_press(SelectorMessage::Change),
|
||||||
.on_press(SelectorMessage::Change),
|
|
||||||
button("Словарь")
|
button("Словарь")
|
||||||
.on_press(SelectorMessage::ToDictionary)
|
.on_press(SelectorMessage::ToDictionary)
|
||||||
.style(Self::nav_style),
|
.style(Self::nav_style),
|
||||||
|
|||||||
+7
-11
@@ -2,11 +2,11 @@ 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};
|
||||||
use crate::pages::sync::SyncMessage::*;
|
use crate::pages::sync::SyncMessage::*;
|
||||||
use crate::jl_button;
|
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use crate::{push_error, push_error_task};
|
use crate::{push_error, push_error_task};
|
||||||
use iced::widget::button::danger;
|
use iced::widget::button::danger;
|
||||||
@@ -241,10 +241,8 @@ impl SyncState {
|
|||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
column![
|
column![
|
||||||
jl_button!("Создать сохранение")
|
jl_button!("Создать сохранение").on_press(InitSync),
|
||||||
.on_press(InitSync),
|
jl_button!("Вставить ключ из буфера").on_press(GetKey),
|
||||||
jl_button!("Вставить ключ из буфера")
|
|
||||||
.on_press(GetKey),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,12 +269,10 @@ 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;", [])
|
error!("Ошибка при checkpoint WAL: {e}");
|
||||||
.unwrap_or_else(|e| {
|
0
|
||||||
error!("Ошибка при checkpoint WAL: {e}");
|
});
|
||||||
0
|
|
||||||
});
|
|
||||||
connection
|
connection
|
||||||
.execute("PRAGMA optimize;", [])
|
.execute("PRAGMA optimize;", [])
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
|
|||||||
+2
-10
@@ -1,17 +1,12 @@
|
|||||||
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)]
|
#[derive(Clone)]
|
||||||
pub struct WordState {
|
pub struct WordState {
|
||||||
@@ -137,10 +132,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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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::jl_button;
|
|
||||||
use crate::styling::*;
|
use crate::styling::*;
|
||||||
use iced::widget::*;
|
use iced::widget::*;
|
||||||
use iced::{Element, Fill, Task, alignment};
|
use iced::{Element, Fill, Task, alignment};
|
||||||
@@ -46,10 +46,8 @@ impl NavigatedPage<WritingMessage> for WritingState {
|
|||||||
text!("{}", self.kana).size(48),
|
text!("{}", self.kana).size(48),
|
||||||
self.answers(),
|
self.answers(),
|
||||||
row![
|
row![
|
||||||
jl_button!(text!("{}", self.next_text))
|
jl_button!(text!("{}", self.next_text)).on_press(WritingMessage::Next),
|
||||||
.on_press(WritingMessage::Next),
|
jl_button!("Закончить").on_press(WritingMessage::Back),
|
||||||
jl_button!("Закончить")
|
|
||||||
.on_press(WritingMessage::Back),
|
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING)
|
.spacing(DEFAULT_SPACING)
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-7
@@ -39,13 +39,10 @@ macro_rules! jl_button {
|
|||||||
|
|
||||||
pub fn back_overlay<'a, T: Clone + 'a>(content: Element<'a, T>, back_message: T) -> Element<'a, T> {
|
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]
|
||||||
jl_button!("Назад").on_press(back_message),
|
.spacing(DEFAULT_SPACING)
|
||||||
content
|
.align_x(Left)
|
||||||
]
|
.width(Fill),
|
||||||
.spacing(DEFAULT_SPACING)
|
|
||||||
.align_x(Left)
|
|
||||||
.width(Fill),
|
|
||||||
)
|
)
|
||||||
.center_x(Fill)
|
.center_x(Fill)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
|||||||
+4
-4
@@ -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]
|
||||||
|
|||||||
Reference in new issue
Block a user