From 97c76350e1916bbd1f434670bf6485c46194b29b Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Tue, 18 Aug 2026 12:47:06 +0300 Subject: [PATCH] Migrate to Id type --- src/data_provider/card_sets.rs | 8 ++-- src/data_provider/card_stats.rs | 6 +-- src/data_provider/history.rs | 10 ++--- src/data_provider/words.rs | 16 +++---- src/dictionary.rs | 16 +++---- src/history.rs | 2 +- src/import.rs | 2 +- src/lang.rs | 76 ++++++++++++++++++++++++++------- src/main.rs | 4 +- src/navigation.rs | 7 +-- src/repetition.rs | 3 +- src/repetitions.rs | 18 ++++---- 12 files changed, 106 insertions(+), 62 deletions(-) diff --git a/src/data_provider/card_sets.rs b/src/data_provider/card_sets.rs index 78b9764..6d3027a 100644 --- a/src/data_provider/card_sets.rs +++ b/src/data_provider/card_sets.rs @@ -1,4 +1,4 @@ -use crate::lang::{DeckSettings, OrderMode}; +use crate::lang::{DeckSettings, OrderMode, INVALID_ID}; use rusqlite::Connection; @@ -43,11 +43,11 @@ pub fn add_set(set: &mut DeckSettings, connection: &Connection) { ) .unwrap_or_else(|e| {println!("{}", e); 0}); - set.id = index; + set.id = index.into(); } pub fn update_card_set(set: &mut DeckSettings, connection: &Connection) { - if set.id == 0 { + if set.id == INVALID_ID { add_set(set, connection); } else { connection @@ -66,7 +66,7 @@ pub fn update_card_set(set: &mut DeckSettings, connection: &Connection) { } pub fn delete_set(set: &DeckSettings, connection: &Connection) { - if set.id == 0 { + if set.id == INVALID_ID { return; } connection diff --git a/src/data_provider/card_stats.rs b/src/data_provider/card_stats.rs index 3327cb6..d5204e6 100644 --- a/src/data_provider/card_stats.rs +++ b/src/data_provider/card_stats.rs @@ -1,4 +1,4 @@ -use crate::lang::{DeckSettings, CardStatistics}; +use crate::lang::{DeckSettings, CardStatistics, INVALID_ID}; use rusqlite::Connection; use std::time::Instant; @@ -62,7 +62,7 @@ pub fn add_stat_list(stat: &mut [CardStatistics], connection: &Connection) { let start_index = last_index - (stat.len() as u32) + 1; for (index, id) in (start_index..=last_index).enumerate() { - stat[index].id = id; + stat[index].id = id.into(); } println!("Added {} cards for {:?}", stat.len(), time.elapsed()); @@ -103,7 +103,7 @@ pub fn update_stat_score(stat: &CardStatistics, connection: &Connection) { } pub fn delete_stat(stat: &CardStatistics, connection: &Connection) { - if stat.id == 0 { + if stat.id == INVALID_ID { return; } connection diff --git a/src/data_provider/history.rs b/src/data_provider/history.rs index 7b0a6ec..ff98019 100644 --- a/src/data_provider/history.rs +++ b/src/data_provider/history.rs @@ -1,5 +1,5 @@ use crate::dictionary::app_data_dir; -use crate::lang::WordOpenMode; +use crate::lang::{Id, WordOpenMode}; use chrono::{DateTime, Utc}; use std::fs; use std::fs::{File, OpenOptions}; @@ -7,7 +7,7 @@ use std::io::Write; use std::io::{BufRead, BufReader}; use std::path::PathBuf; -pub fn get_history_of_set(id: u32) -> Vec { +pub fn get_history_of_set(id: Id) -> Vec { let app_dir = history_dir(); let head = app_dir.clone().join(format!("set_{}_history.csv", id)); let mut lines = Vec::new(); @@ -32,7 +32,7 @@ fn parse_history_items(strings: Vec) -> Vec { if let [time, word, mode, before, after] = string.split(';').collect::>()[..] { items.push(HistoryItem { timestamp: DateTime::from_timestamp(time.parse().unwrap(), 0).unwrap(), - word_id: word.parse::().unwrap(), + word_id: word.parse::().unwrap().into(), mode: match mode.parse::().unwrap() { 2 => WordOpenMode::Hard, 3 => WordOpenMode::Ok, @@ -48,7 +48,7 @@ fn parse_history_items(strings: Vec) -> Vec { items } -pub fn push_note(set_id: u32, item: HistoryItem) { +pub fn push_note(set_id: Id, item: HistoryItem) { let app_dir = history_dir(); let path = app_dir.clone().join(format!("set_{}_history.csv", set_id)); @@ -84,7 +84,7 @@ pub fn history_dir() -> PathBuf { #[derive(Clone)] pub struct HistoryItem { pub timestamp: DateTime, - pub word_id: u32, + pub word_id: Id, pub mode: WordOpenMode, pub before: u8, pub after: u8, diff --git a/src/data_provider/words.rs b/src/data_provider/words.rs index a90fe4a..5947a96 100644 --- a/src/data_provider/words.rs +++ b/src/data_provider/words.rs @@ -1,4 +1,4 @@ -use crate::lang::{WordData, WordGroup}; +use crate::lang::{WordData, WordGroup, INVALID_ID}; use rusqlite::{Connection, params}; use std::collections::HashMap; @@ -20,7 +20,7 @@ pub fn add_word(word: &mut WordData, connection: &Connection) { 0 }); - word.id = index; + word.id = index.into(); } pub fn add_words(words: &mut [WordData], connection: &mut Connection) { @@ -59,12 +59,12 @@ pub fn add_words(words: &mut [WordData], connection: &mut Connection) { let start_index = last_index - (count as u32) + 1; for (index, id) in (start_index..=last_index).enumerate() { - words[index].id = id; + words[index].id = id.into(); } } pub fn update_word(word: &mut WordData, connection: &Connection) { - if word.id == 0 { + if word.id == INVALID_ID { add_word(word, connection); } else { connection @@ -86,7 +86,7 @@ pub fn update_word(word: &mut WordData, connection: &Connection) { } pub fn delete_word(word: &WordData, connection: &Connection) { - if word.id == 0 { + if word.id == INVALID_ID { return; } connection @@ -156,11 +156,11 @@ pub fn add_group(group: &mut WordGroup, connection: &Connection) { 0 }); - group.id = index; + group.id = index.into(); } pub fn update_group(group: &mut WordGroup, connection: &Connection) { - if group.id == 0 { + if group.id == INVALID_ID { add_group(group, connection); } else { connection @@ -176,7 +176,7 @@ pub fn update_group(group: &mut WordGroup, connection: &Connection) { } pub fn delete_group(group: &WordGroup, connection: &Connection) { - if group.id == 0 { + if group.id == INVALID_ID { return; } connection diff --git a/src/dictionary.rs b/src/dictionary.rs index 3e00195..30f2c3b 100644 --- a/src/dictionary.rs +++ b/src/dictionary.rs @@ -2,7 +2,7 @@ use crate::data_provider::words::{delete_group, delete_word, update_group, updat use crate::dictionary::DictionaryMessage::*; use crate::dictionary_test::DictionaryQuizState; use crate::import::ImportState; -use crate::lang::{WordData, WordGroup}; +use crate::lang::{WordData, WordGroup, INVALID_ID}; use crate::navigation::Page::{Import, Word}; use crate::navigation::{NavigatedPage, Page}; use crate::styling::*; @@ -96,7 +96,7 @@ impl NavigatedPage for DictionaryState { let dict = &state.dictionary; word = dict[*index].clone(); } - if word.id != 0 { + if word.id != INVALID_ID { return Some(Word(WordState::new(word, *index, self.state.clone()))); } } @@ -172,7 +172,7 @@ impl NavigatedPage for DictionaryState { } Include(i, b) => self.include_map[i] = b, IncludeTag(t, v) => { - let index: u32; + let index; { let state = self.state.lock().unwrap(); index = state.word_groups[self.selected_group_index].id; @@ -197,7 +197,7 @@ impl NavigatedPage for DictionaryState { let state = &mut self.state.lock().unwrap(); state.word_groups.push(WordGroup { - id: 0, + id: 0.into(), name: format!("Группа слов {}", random_range(100..1000)), }); } @@ -222,7 +222,7 @@ impl NavigatedPage for DictionaryState { } SelectGroup(i) => { self.selected_group_index = i; - let index: u32; + let index; { let state = self.state.lock().unwrap(); index = state.word_groups[i].id; @@ -423,7 +423,7 @@ impl DictionaryState { let line_button = || { let action = WordAction(index); - if data.id == 0 { + if data.id == INVALID_ID { return button("-").on_press(action).style(|_x, _status| Style { background: None, text_color: Color::BLACK, @@ -532,7 +532,7 @@ impl DictionaryState { }); } - fn update_words_include(&mut self, group_id: u32) { + fn update_words_include(&mut self, group_id: crate::lang::Id) { let include_tags = self .tag_map .iter() @@ -626,6 +626,6 @@ struct WordLineState { key: String, value: String, tags: String, - id: u32, + id: crate::lang::Id, index: usize, } diff --git a/src/history.rs b/src/history.rs index a45d062..d055d2f 100644 --- a/src/history.rs +++ b/src/history.rs @@ -46,7 +46,7 @@ impl NavigatedPage for HistoryState { } impl HistoryState { - pub fn new(id: u32, state: Arc>) -> Self { + pub fn new(id: crate::lang::Id, state: Arc>) -> Self { let state = state.lock().unwrap(); let history = get_history_of_set(id); let words = history diff --git a/src/import.rs b/src/import.rs index c2c694b..e3cff93 100644 --- a/src/import.rs +++ b/src/import.rs @@ -446,7 +446,7 @@ impl ImportState { let map_indices = Self::get_mapping_indices(&group); let mut group_entity = WordGroup { - id: 0, + id: 0.into(), name: group.name.clone(), }; { diff --git a/src/lang.rs b/src/lang.rs index 689dc6d..78d3da7 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -11,8 +11,11 @@ use rayon::iter::IndexedParallelIterator; use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; use rhai::{Engine, Scope}; -use serde::{Deserialize, Serialize}; +use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; +use rusqlite::ToSql; +use std::cmp::PartialEq; use std::collections::HashMap; +use std::fmt::{Display, Formatter}; use std::sync::{Arc, Mutex}; use std::time::Instant; @@ -20,6 +23,9 @@ const MAX_HISTORY_LEN: usize = 20; const MAX_HISTORY_LEN_PART: f32 = 0.33; const MAX_SCORE: u8 = 25; const FADE_PER_DAY: f32 = 0.95; +#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy)] +pub(crate) struct Id(u32); +pub const INVALID_ID: Id = Id(0); #[derive(Clone, Debug)] pub struct KanaSet { name: String, @@ -225,40 +231,40 @@ impl PartialEq for KanaSet { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub struct WordData { - pub id: u32, + pub id: Id, pub key: String, pub value: String, pub tags: String, pub additional: HashMap, - pub group_id: u32, + pub group_id: Id, } impl WordData { pub fn new() -> Self { Self { - id: 0, + id: 0.into(), key: String::new(), value: String::new(), tags: String::new(), additional: Default::default(), - group_id: 1, + group_id: 1.into(), } } } #[derive(Clone)] pub struct WordGroup { - pub id: u32, + pub id: Id, pub name: String, } #[derive(Clone, PartialEq)] pub struct CardStatistics { - pub id: u32, - pub word_id: u32, - pub set_id: u32, + pub id: Id, + pub word_id: Id, + pub set_id: Id, pub last_open: DateTime, pub score: u8, } @@ -320,8 +326,8 @@ impl DeckData { .map(|w| state_locked.dictionary.get(*w).unwrap()) .cloned() .collect(); - let word_ids = last_list.iter().map(|l| l.id).collect::>(); - + let word_ids = last_list.iter().map(|l| l.id).collect::>(); + let mut index = 0; for stat in current_set.clone() { if !word_ids.contains(&stat.word_id) { @@ -410,7 +416,7 @@ impl DeckData { self.settings.id, HistoryItem { timestamp: Utc::now(), - word_id: word.word_id, + word_id: word.word_id.into(), mode: WordOpenMode::Easy, before: old_score, after: new_score, @@ -606,7 +612,7 @@ impl WorstWordsSRSModule { #[derive(Clone)] pub struct DeckSettings { - pub id: u32, + pub id: Id, pub name: String, pub forward: String, pub backward: String, @@ -619,7 +625,7 @@ pub struct DeckSettings { impl DeckSettings { pub(crate) fn with_name(name: String) -> DeckSettings { DeckSettings { - id: 0, + id: 0.into(), name, forward: "".to_string(), backward: "".to_string(), @@ -661,7 +667,7 @@ impl DeckSettings { } let mut scope = Scope::new(); scope - .push_constant("id", word.id) + .push_constant("id", word.id.0) .push_constant("key", word.key.clone()) .push_constant("value", word.value.clone()) .push_constant("tags", word.tags.clone()) @@ -691,3 +697,41 @@ impl DeckSettings { self.forward == "speech" || self.backward == "speech" } } + +impl Into for u32{ + fn into(self) -> Id { + Id(self) + } +} + +// impl From for Id{ +// fn from(id: u32) -> Id{ +// Id(id) +// } +// } + +impl Into for Id{ + fn into(self) -> u32 { + self.0 + } +} +impl Display for Id { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromSql for Id{ + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Integer(i) => Ok(Id(i as u32)), + _ => Err(FromSqlError::InvalidType), + } + } +} + +impl ToSql for Id{ + fn to_sql(&self) -> rusqlite::Result> { + Ok(ToSqlOutput::from(self.0 as i64)) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 12281c5..e2e95fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use crate::data_provider::card_sets::load_sets; use crate::data_provider::settings::get_setting; use crate::data_provider::sqlite::default_connection; use crate::data_provider::words::{load_word_groups, load_words}; -use crate::lang::{DeckSettings, WordData, WordGroup}; +use crate::lang::{DeckSettings, Id, WordData, WordGroup}; use crate::navigation::{AppSettings, RootMessage, ScreenState}; use crate::quiz::*; use chrono::NaiveDate; @@ -85,7 +85,7 @@ pub struct AppState { pub word_groups: Vec, pub connection: Connection, pub sync_data: AppSettings, - pub activity: HashMap>, + pub activity: HashMap>, } impl Default for AppState { diff --git a/src/navigation.rs b/src/navigation.rs index f8bbbed..1e3e7b1 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -31,6 +31,7 @@ use hashbrown::HashMap; use std::sync::{Arc, Mutex}; use std::time::Instant; use rusqlite::Connection; +use crate::lang::Id; impl Default for ScreenState { fn default() -> Self { @@ -60,7 +61,7 @@ pub enum RootMessage { RepetitionSettings(RepetitionSettingsMessage), Import(ImportMessage), Keyboard(Event), - DataLoaded(HashMap>), + DataLoaded(HashMap>), None, UpdateData, } @@ -126,9 +127,9 @@ impl ScreenState { for file in directory.read_dir().unwrap().flatten() { let mut vec = vec![]; let history_file_name = file.file_name().into_string().unwrap(); - let id = history_file_name[4..history_file_name.len() - 12] + let id : Id = history_file_name[4..history_file_name.len() - 12] .parse::() - .unwrap(); + .unwrap().into(); let history = get_history_of_set(id); let by_date = history.chunk_by(|x, x1| { diff --git a/src/repetition.rs b/src/repetition.rs index 7985083..efb38b4 100644 --- a/src/repetition.rs +++ b/src/repetition.rs @@ -13,6 +13,7 @@ use iced::{Element, Fill, Task, Theme, alignment, keyboard}; use rodio::MixerDeviceSink; use std::collections::HashSet; use std::sync::{Arc, Mutex}; +use crate::lang::Id; use tokio::task::spawn_blocking; pub struct RepetitionState { @@ -24,7 +25,7 @@ pub struct RepetitionState { open: bool, can_play: bool, sink: Arc, - opened: HashSet, + opened: HashSet, } impl NavigatedPage for RepetitionState { diff --git a/src/repetitions.rs b/src/repetitions.rs index e2095fb..d57f249 100644 --- a/src/repetitions.rs +++ b/src/repetitions.rs @@ -27,14 +27,12 @@ use std::sync::{Arc, Mutex}; #[derive(Clone)] pub struct RepetitionsState { - selected_set_index: Option, correct_filters: Vec, current_sets_cards_cache: HashMap, Vec)>, word_id_index_map: HashMap, local_settings: HashMap, view_data: Option, - pub state: Arc>, - set_names: Vec + state: Arc>, } impl NavigatedPage for RepetitionsState { @@ -89,7 +87,9 @@ impl NavigatedPage for RepetitionsState { GoToHistory => {} GoToSettings => {} - CreateSet => {} + CreateSet => { + + } DeleteSet => {} SetName(_) => {} SelectSet(_) => {} @@ -209,15 +209,15 @@ impl RepetitionsState { button("Проверить фильтр") .style(jl_button) .on_press(TryFilter), - self.count_view(&set), + self.count_view(set), ] .spacing(QUARTER_SPACING), ] .spacing(DEFAULT_SPACING) } else { column![ - self.filled_set_data_view(&set), - self.word_append_panel(&set), + self.filled_set_data_view(set), + self.word_append_panel(set), radio( "Обычный режим", OrderMode::Default, @@ -430,7 +430,6 @@ impl RepetitionsState { impl RepetitionsState { fn append_all_words(&self, set: &DeckSettings) { - let index = self.selected_set_index.unwrap(); let cache = &self.current_sets_cards_cache[&index]; let mut created_set = HashSet::with_capacity(cache.0.len()); cache.0.iter().for_each(|c| { @@ -461,13 +460,12 @@ impl RepetitionsState { fn select_set(&mut self, index: usize) { let state = self.state.lock().unwrap(); if let Some(set) = state.card_sets.get(index) { - self.selected_set_index = Some(index); } } fn clear_selection(&mut self) { - self.selected_set_index = None; + self.view_data = None; } } #[derive(Clone)]