diff --git a/src/app.rs b/src/app.rs index aec8db7..3ff506d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -94,9 +94,9 @@ pub fn fill_state(state: &mut AppState) { let groups = load_word_groups(&state.connection); let setting = load_settings(&state.connection); - state.dictionary = list; + state.dictionary = list.unwrap_or_default(); state.decks = sets.unwrap_or_default(); - state.word_groups = groups; + state.word_groups = groups.unwrap_or_default(); state.sync_data = setting } diff --git a/src/data_provider/card_sets.rs b/src/data_provider/card_sets.rs index d5c5230..b4bb18a 100644 --- a/src/data_provider/card_sets.rs +++ b/src/data_provider/card_sets.rs @@ -24,10 +24,7 @@ pub fn load_sets(connection: &Connection) -> Result, Notificat .map_err(|_| DbLoadError)?; let mut buffer = Vec::with_capacity(16); - for word in iter.filter_map(|result| result.ok()) { - buffer.push(word); - } - + iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i)); Ok(buffer) } diff --git a/src/data_provider/card_stats.rs b/src/data_provider/card_stats.rs index bac5ccb..95cf49d 100644 --- a/src/data_provider/card_stats.rs +++ b/src/data_provider/card_stats.rs @@ -25,9 +25,7 @@ pub fn load_stats_of_deck( .map_err(|_| DbLoadError)?; let mut buffer = Vec::with_capacity(512); - for word in iter.filter_map(|result| result.ok()) { - buffer.push(word); - } + iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i)); Ok(buffer) } diff --git a/src/data_provider/words.rs b/src/data_provider/words.rs index 68aec73..178cfb6 100644 --- a/src/data_provider/words.rs +++ b/src/data_provider/words.rs @@ -1,8 +1,10 @@ +use crate::data::entities::{WordData, WordGroup}; +use crate::notification::NotificationData; +use crate::notification::NotificationData::{DbLoadError, DbSaveError}; use rusqlite::{Connection, params}; use std::collections::HashMap; -use crate::data::entities::{WordData, WordGroup}; -pub fn add_word(word: &mut WordData, connection: &Connection) { +pub fn add_word(word: &mut WordData, connection: &Connection) -> Result<(), NotificationData> { let index : u32 = connection .query_row( "INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5) RETURNING id", @@ -10,17 +12,21 @@ pub fn add_word(word: &mut WordData, connection: &Connection) { &word.key, &word.value, &word.tags, - serde_json::to_string(&word.additional).unwrap(), + serde_json::to_string(&word.additional).unwrap_or_default(), &word.group_id, ), |row| row.get(0), - ).unwrap(); + ).map_err(|_| DbSaveError)?; word.id = index.into(); + Ok(()) } -pub fn add_words(words: &mut [WordData], connection: &mut Connection) { - let tx = connection.transaction().unwrap(); +pub fn add_words( + words: &mut [WordData], + connection: &mut Connection, +) -> Result<(), NotificationData> { + let tx = connection.transaction().map_err(|_| DbLoadError)?; let count = words.len(); { @@ -28,33 +34,35 @@ pub fn add_words(words: &mut [WordData], connection: &mut Connection) { .prepare( "INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)", ) - .unwrap(); + .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(), + serde_json::to_string(&word.additional).unwrap_or_default(), word.group_id ]) - .unwrap(); + .map_err(|_| DbSaveError)?; } } let last_index: u32 = tx.last_insert_rowid() as u32; - tx.commit().unwrap(); + 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) { +pub fn update_word(word: &mut WordData, connection: &Connection) -> Result<(), NotificationData> { if !word.id.is_valid() { - add_word(word, connection); + Ok(add_word(word, connection)?) } else { connection .execute( @@ -67,23 +75,25 @@ pub fn update_word(word: &mut WordData, connection: &Connection) { &word.id, ), ) - .unwrap(); + .map_err(|_| DbSaveError)?; + Ok(()) } } -pub fn delete_word(word: &WordData, connection: &Connection) { +pub fn delete_word(word: &WordData, connection: &Connection) -> Result<(), NotificationData> { if !word.id.is_valid() { - return; + return Err(DbSaveError); } connection .execute("DELETE FROM words WHERE id = ?1", (&word.id,)) - .unwrap(); + .map_err(|_| DbSaveError)?; + Ok(()) } -pub fn load_words(connection: &Connection) -> Vec { +pub fn load_words(connection: &Connection) -> Result, NotificationData> { let mut stmt = connection .prepare("SELECT id, key, value, tags, more, group_id FROM words") - .unwrap(); + .expect("Failed to prepare statement"); let word_iter = stmt .query_map([], |row| { let additional: String = row.get(4)?; @@ -92,24 +102,22 @@ pub fn load_words(connection: &Connection) -> Vec { key: row.get(1)?, value: row.get(2)?, tags: row.get(3)?, - additional: serde_json::from_str::>(&additional).unwrap(), + additional: serde_json::from_str::>(&additional).unwrap_or_default(), group_id: row.get(5)?, }) }) - .unwrap(); + .map_err(|_| DbLoadError)?; - let mut buffer = vec![]; - for word in word_iter { - buffer.push(word.unwrap()); - } + let mut buffer = Vec::with_capacity(1024); + word_iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i)); - buffer + Ok(buffer) } -pub fn load_word_groups(connection: &Connection) -> Vec { +pub fn load_word_groups(connection: &Connection) -> Result, NotificationData> { let mut stmt = connection .prepare("SELECT id, name FROM word_group") - .unwrap(); + .expect("Failed to prepare statement"); let group_iter = stmt .query_map([], |row| { Ok(WordGroup { @@ -117,46 +125,50 @@ pub fn load_word_groups(connection: &Connection) -> Vec { name: row.get(1)?, }) }) - .unwrap(); + .map_err(|_| DbLoadError)?; - let mut buffer = vec![]; - for group in group_iter { - buffer.push(group.unwrap()); - } + let mut buffer = Vec::with_capacity(8); + group_iter.filter_map(|res| res.ok()).for_each(|i| buffer.push(i)); - buffer + Ok(buffer) } -pub fn add_group(group: &mut WordGroup, connection: &Connection) { +pub fn add_group(group: &mut WordGroup, connection: &Connection) -> Result<(), NotificationData> { let index: u32 = connection .query_row( "INSERT INTO word_group (name) VALUES (?1) RETURNING id", (&group.name,), |row| row.get(0), ) - .unwrap(); + .map_err(|_| DbSaveError)?; group.id = index.into(); + Ok(()) } -pub fn update_group(group: &mut WordGroup, connection: &Connection) { +pub fn update_group( + group: &mut WordGroup, + connection: &Connection, +) -> Result<(), NotificationData> { if !group.id.is_valid() { - add_group(group, connection); + Ok(add_group(group, connection)?) } else { connection .execute( "UPDATE word_group SET name = ?1 WHERE id = ?2", (&group.name, &group.id), ) - .unwrap(); + .map_err(|_| DbSaveError)?; + Ok(()) } } -pub fn delete_group(group: &WordGroup, connection: &Connection) { +pub fn delete_group(group: &WordGroup, connection: &Connection) -> Result<(), NotificationData> { if !group.id.is_valid() { - return; + return Err(DbSaveError); } connection .execute("DELETE FROM word_group WHERE id = ?1", (&group.id,)) - .unwrap(); + .map_err(|_| DbSaveError)?; + Ok(()) } diff --git a/src/pages/dictionary.rs b/src/pages/dictionary.rs index cc808dc..b800564 100644 --- a/src/pages/dictionary.rs +++ b/src/pages/dictionary.rs @@ -1,12 +1,16 @@ +use crate::app::AppState; use crate::data::Id; +use crate::data::entities::{WordData, WordGroup}; use crate::data_provider::words::{delete_group, delete_word, update_group, update_word}; +use crate::navigation::Page::{Import, Word}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::pages::dictionary::DictionaryMessage::*; use crate::pages::dictionary_quiz::DictionaryQuizState; use crate::pages::import::ImportState; -use crate::navigation::Page::{Import, Word}; -use crate::navigation::{NavigatedPage, Page, RootMessage}; -use crate::styling::*; use crate::pages::word::WordState; +use crate::push_error; +use crate::push_error_task; +use crate::styling::*; use chrono::{DateTime, TimeDelta, Utc}; use hashbrown::{HashMap, HashSet}; use iced::alignment::Vertical::Center; @@ -17,14 +21,12 @@ use iced::widget::text_input::default; use iced::widget::*; use iced::{Border, Color, Shadow, Task}; use iced_core::Length::Fill; +use log::info; use parking_lot::Mutex; use rand::random_range; use std::ops::Add; use std::sync::Arc; use std::time::{Duration, Instant}; -use log::info; -use crate::app::AppState; -use crate::data::entities::{WordData, WordGroup}; #[derive(Clone)] pub struct DictionaryState { @@ -168,7 +170,9 @@ impl NavigatedPage for DictionaryState { let word = dict.remove(i); self.include_map.remove(i); self.auto_save_queue.remove(&i); - delete_word(&word, &state.connection) + if let Err(err) = delete_word(&word, &state.connection) { + return push_error_task!(err); + } } Include(i, b) => self.include_map[i] = b, IncludeTag(t, v) => { @@ -190,7 +194,7 @@ impl NavigatedPage for DictionaryState { self.search = s; } SetTyping(b) => self.no_typing = b, - SubmitWord(i) => self.save_word(i), + SubmitWord(i) => return self.save_word(i), Back => {} Test => {} CreateGroup => { @@ -213,7 +217,10 @@ impl NavigatedPage for DictionaryState { let connection = &state.connection; let mut group = state.word_groups[self.selected_group_index].clone(); - update_group(&mut group, connection); + if let Err(err) = update_group(&mut group, connection) { + return push_error_task!(err); + } + state.word_groups[self.selected_group_index] = group; } SelectGroup(i) => { @@ -236,7 +243,10 @@ impl NavigatedPage for DictionaryState { let remove_group_id = group.id; let connection = &state.connection; - delete_group(group, connection); + if let Err(err) = delete_group(group, connection) { + return push_error_task!(err); + } + state.word_groups.remove(self.selected_group_index); state .dictionary @@ -257,7 +267,8 @@ impl NavigatedPage for DictionaryState { } if self.auto_save_queue[&word_index] <= now { self.auto_save_queue.remove(&word_index); - self.save_word(word_index); + + return self.save_word(word_index); } } ToImport => {} @@ -309,13 +320,17 @@ impl DictionaryState { result } - fn save_word(&mut self, i: usize) { + fn save_word(&mut self, i: usize) -> Task { let state = &mut self.state.lock(); let connection = &state.connection; let word = &mut state.dictionary[i].clone(); - update_word(word, connection); + if let Err(err) = update_word(word, connection) { + return push_error_task!(err); + } + state.dictionary[i] = word.clone(); + Task::none() } fn launch_auto_save_offset(&mut self, index: usize) -> Task { diff --git a/src/pages/import.rs b/src/pages/import.rs index 85c2fd5..91fbb93 100644 --- a/src/pages/import.rs +++ b/src/pages/import.rs @@ -444,7 +444,7 @@ impl ImportState { }; { let state = state.lock(); - add_group(&mut group_entity, &state.connection); + add_group(&mut group_entity, &state.connection)?; } let group_id = group_entity.id; @@ -484,7 +484,7 @@ impl ImportState { let mut index = 0; let total_len = words_list.len() as f32 / 1024.0; for word in &mut words_list.chunks_mut(1024) { - add_words(word, connection); + add_words(word, connection)?; index += 1; let mut progress = progress.lock(); *progress = (index as f32 / total_len).min(1.0); diff --git a/src/pages/word.rs b/src/pages/word.rs index d7e6a13..fa95d58 100644 --- a/src/pages/word.rs +++ b/src/pages/word.rs @@ -1,15 +1,17 @@ -use crate::data_provider::words::{delete_word, update_word}; +use crate::app::AppState; use crate::data::entities::WordData; +use crate::data_provider::words::{delete_word, update_word}; use crate::navigation::Page::PreviousPage; use crate::navigation::{NavigatedPage, Page, RootMessage}; -use crate::styling::*; use crate::pages::word::WordMessage::*; +use crate::push_error; +use crate::push_error_task; +use crate::styling::*; use iced::widget::button::danger; use iced::widget::{button, column, row, rule, scrollable, text, text_input}; use iced::{Element, Fill, Task}; use parking_lot::Mutex; use std::sync::Arc; -use crate::app::AppState; #[derive(Clone)] pub struct WordState { @@ -34,13 +36,18 @@ impl NavigatedPage for WordState { Save => { let mut state = self.state.lock(); state.dictionary[self.index] = self.word.clone(); - update_word(&mut self.word, &state.connection); + + if let Err(err) = update_word(&mut self.word, &state.connection) { + return push_error_task!(err); + } return Task::done(RootMessage::Word(Back)); } Delete => { let mut state = self.state.lock(); state.dictionary.remove(self.index); - delete_word(&self.word, &state.connection); + if let Err(err) = delete_word(&self.word, &state.connection) { + return push_error_task!(err); + } return Task::done(RootMessage::Word(Back)); } SetTags(n) => self.word.tags = n,