diff --git a/Cargo.lock b/Cargo.lock index 04b8d43..4eab091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,6 +2456,7 @@ dependencies = [ "hex", "iced", "iced_core", + "litemap", "log", "mimalloc", "parking_lot", @@ -2739,9 +2740,12 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +dependencies = [ + "serde_core", +] [[package]] name = "litrs" diff --git a/Cargo.toml b/Cargo.toml index c715c06..7f51155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ parking_lot = "0.12.5" env_logger = "0.11.11" log = "0.4.34" fsrs = "6.6.1" +litemap = { version = "0.8.3", features = ["serde"] } [profile.super-release] inherits = "release" diff --git a/src/app.rs b/src/app.rs index eaafa91..8eac209 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,6 +19,7 @@ use rusqlite::Connection; pub const USER_FONT: Font = Font::with_name("Noto Sans JP"); pub fn start() -> iced::Result { + println!("{}", size_of::()); info!("Welcome to JapLearn"); iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) .window(window_settings()) diff --git a/src/data.rs b/src/data.rs index e575bf5..60f1fd8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -7,57 +7,64 @@ use std::fmt::{write, Display, Formatter}; use std::num::ParseIntError; use std::str::FromStr; -#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy, Ord, PartialOrd, Default)] -pub struct Id(pub(crate) u32); -pub const INVALID_ID: Id = Id(0); -impl FromStr for Id { - type Err = ParseIntError; +macro_rules! id_impl { + ($id_type:ty, $name:ident, $invalid:ident) => { + #[derive(Clone, PartialEq, Eq, Debug, Hash, Copy, Ord, PartialOrd, Default)] + pub struct $name(pub(crate) $id_type); + pub const $invalid: $name = $name(0); + impl FromStr for $name { + type Err = ParseIntError; - fn from_str(s: &str) -> Result { - Ok(Id(s.parse::()?)) - } -} + fn from_str(s: &str) -> Result { + Ok($name(s.parse::<$id_type>()?)) + } + } -impl From for Id { - fn from(value: u32) -> Self { - Id(value) - } -} + impl From<$id_type> for $name { + fn from(value: $id_type) -> Self { + $name(value) + } + } -impl From for u32 { - fn from(value: Id) -> Self { - value.0 - } -} -impl Display for Id { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} + impl From<$name> for $id_type { + fn from(value: $name) -> Self { + value.0 + } + } + impl Display for $name { + 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 FromSql for $name { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Integer(i) => Ok($name(i as $id_type)), + _ => Err(FromSqlError::InvalidType), + } + } + } + + impl ToSql for $name { + fn to_sql(&self) -> rusqlite::Result> { + Ok(ToSqlOutput::from(self.0 as i64)) + } + } + + impl From<$name> for String { + fn from(value: $name) -> Self { + value.0.to_string() + } + } + + impl $name { + pub(crate) fn is_valid(self) -> bool { + self.0 != 0 + } } } } -impl ToSql for Id { - fn to_sql(&self) -> rusqlite::Result> { - Ok(ToSqlOutput::from(self.0 as i64)) - } -} - -impl From for String { - fn from(value: Id) -> Self { - value.0.to_string() - } -} - -impl Id { - pub(crate) fn is_valid(self) -> bool { - self.0 != 0 - } -} \ No newline at end of file +id_impl!(u32, Id, INVALID_ID); +id_impl!(u16, SmallId, INVALID_SMALL_ID); diff --git a/src/data/entities.rs b/src/data/entities.rs index 3c025db..7410848 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -7,18 +7,18 @@ use log::info; use rayon::prelude::*; use rhai::{Engine, Scope}; use std::cmp::Ordering; -use std::collections::HashMap; use std::marker::PhantomData; use std::time::Instant; +use litemap::LiteMap; #[derive(Clone, Debug)] pub struct WordData { pub id: Id, + pub group_id: Id, pub key: String, pub value: String, pub tags: String, - pub additional: HashMap, - pub group_id: Id, + pub additional: LiteMap, } impl EntityData for WordData { @@ -37,6 +37,16 @@ pub struct WordGroup { pub name: String, } +impl EntityData for WordGroup { + fn id(&self) -> Id { + self.id + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + impl WordData { pub fn new() -> Self { Self { @@ -155,6 +165,17 @@ pub struct CardState { pub fsrs_memory: Option, pub fsrs_open_day: NaiveDate, } + +impl EntityData for CardState { + fn id(&self) -> Id { + self.id + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + impl CardState { pub fn update(&mut self, status: WordOpenMode) { match status { diff --git a/src/data_provider/sqlite.rs b/src/data_provider/sqlite.rs index c885e9c..800f70f 100644 --- a/src/data_provider/sqlite.rs +++ b/src/data_provider/sqlite.rs @@ -65,8 +65,13 @@ pub fn default_connection() -> Connection { .execute("PRAGMA foreign_keys = ON;", []) .expect("Failed to set PRAGMA"); connection - .execute("PRAGMA journal_mode = WAL;", []) - .expect("Failed to set PRAGMA"); + .query_one("PRAGMA journal_mode = WAL;", [], |row| { + println!("{:?}", row); + Ok(()) + }) + .unwrap_or_else(|e| { + println!("{}", e); + }); connection .execute("PRAGMA synchronous = NORMAL;", []) .expect("Failed to set PRAGMA"); diff --git a/src/data_provider/words.rs b/src/data_provider/words.rs index 9eff98d..75587ed 100644 --- a/src/data_provider/words.rs +++ b/src/data_provider/words.rs @@ -1,8 +1,96 @@ use crate::data::entities::{WordData, WordGroup}; +use crate::data_provider::TableAccessQuerySet; use crate::notification::NotificationData::{DbLoadError, DbSaveError}; use crate::notification::{MaybeError, NotificationData}; -use rusqlite::{Connection, params}; +use rusqlite::{Connection, Error, Params, Row, params}; use std::collections::HashMap; +use litemap::LiteMap; + +pub struct WordQuerySet; +impl TableAccessQuerySet for WordQuerySet { + fn load() -> &'static str { + "SELECT id, key, value, tags, more, group_id FROM words" + } + + fn save() -> &'static str { + "UPDATE words SET key = ?1, value = ?2, tags = ?3, more = ?4 WHERE id = ?5" + } + + fn add() -> &'static str { + "INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)" + } + + fn name() -> &'static str { + "words" + } + + fn from_sql(row: &Row) -> Result { + 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)?, + }) + } + + fn insert_params(entity: &WordData) -> impl Params { + ( + &entity.key, + &entity.value, + &entity.tags, + serde_json::to_string(&entity.additional).unwrap_or_default(), + entity.group_id, + ) + } + + fn update_params(entity: &WordData) -> impl Params { + ( + &entity.key, + &entity.value, + &entity.tags, + serde_json::to_string(&entity.additional).unwrap_or_default(), + entity.id, + ) + } +} + +pub struct WordGroupQuerySet; +impl TableAccessQuerySet for WordGroupQuerySet { + fn load() -> &'static str { + "SELECT id, name FROM word_groups" + } + + fn save() -> &'static str { + "UPDATE word_groups SET name = ?1 WHERE id = ?2" + } + + fn add() -> &'static str { + "INSERT INTO word_groups (name) VALUES (?1)" + } + + fn name() -> &'static str { + "word_groups" + } + + fn from_sql(row: &Row) -> Result { + Ok(WordGroup { + id: row.get(0)?, + name: row.get(1)?, + }) + } + + fn insert_params(entity: &WordGroup) -> impl Params { + (&entity.name,) + } + + fn update_params(entity: &WordGroup) -> impl Params { + (&entity.name, entity.id) + } +} pub fn add_word(word: &mut WordData, connection: &Connection) -> MaybeError { let index : u32 = connection @@ -99,7 +187,7 @@ pub fn load_words(connection: &Connection) -> Result, Notification key: row.get(1)?, value: row.get(2)?, tags: row.get(3)?, - additional: serde_json::from_str::>(&additional) + additional: serde_json::from_str(&additional) .unwrap_or_default(), group_id: row.get(5)?, }) diff --git a/src/notification.rs b/src/notification.rs index d519a88..bc6c025 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -139,7 +139,7 @@ impl NotificationManager { fn draw_data<'a>(data: &NotificationData) -> Element<'a, NotificationMessage> { match data { NotificationData::DataLoaded => { - text!("Данные успешно скачаны") + text!("Данные успешно загружены на сервер") } NotificationData::ImportError => { text!("Ошибка при импорте данных из Anki") @@ -154,7 +154,7 @@ impl NotificationManager { text!("Не получается подключиться к VoiceVox") } NotificationData::PermissionDenied => { - text!("У приложение недостаточно прав") + text!("У приложение недостаточно прав для доступа в файлам") } NotificationData::WebApiUnavailable => { text!("Не получается подключиться к Api")