diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5df899b..826c26f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -673,6 +673,7 @@ dependencies = [ "base64 0.22.1", "chrono", "image", + "rusqlite", "serde", "serde_json", "tauri", @@ -1078,7 +1079,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.9", ] [[package]] @@ -1337,6 +1338,18 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.3.0" @@ -1944,6 +1957,18 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] [[package]] name = "heck" @@ -2627,6 +2652,17 @@ dependencies = [ "system-deps 7.0.7", ] +[[package]] +name = "libsqlite3-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libwayshot-xcap" version = "0.3.2" @@ -4234,6 +4270,31 @@ version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + +[[package]] +name = "rusqlite" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -4691,6 +4752,18 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5741,6 +5814,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8aa8f2a..c642475 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -31,6 +31,7 @@ chrono = "0.4.44" tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "time"] } anyhow = "1.0.102" base64 = "0.22.1" +rusqlite = { version = "0.39.0", features = ["bundled"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-autostart = "2" diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs new file mode 100644 index 0000000..094120a --- /dev/null +++ b/src-tauri/src/db.rs @@ -0,0 +1,135 @@ +use rusqlite::{params, Connection}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize, Clone)] +pub struct Tag { + pub id: i64, + pub name: String, + pub parent_id: Option, + pub color: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Event { + pub id: i64, + pub date: String, + pub start_minute: i32, + pub end_minute: i32, + pub main_tag_id: i64, + pub sub_tag_id: Option, + pub content: String, +} + +pub fn init_db(path: &str) -> anyhow::Result<()> { + let conn = Connection::open(path)?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_id INTEGER, + color TEXT NOT NULL, + FOREIGN KEY(parent_id) REFERENCES tags(id) + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + start_minute INTEGER NOT NULL, + end_minute INTEGER NOT NULL, + main_tag_id INTEGER NOT NULL, + sub_tag_id INTEGER, + content TEXT, + FOREIGN KEY(main_tag_id) REFERENCES tags(id), + FOREIGN KEY(sub_tag_id) REFERENCES tags(id) + )", + [], + )?; + + Ok(()) +} + +pub fn get_tags(path: &str) -> anyhow::Result> { + let conn = Connection::open(path)?; + let mut stmt = conn.prepare("SELECT id, name, parent_id, color FROM tags")?; + let tag_iter = stmt.query_map([], |row| { + Ok(Tag { + id: row.get(0)?, + name: row.get(1)?, + parent_id: row.get(2)?, + color: row.get(3)?, + }) + })?; + + let mut results = Vec::new(); + for tag in tag_iter { + results.push(tag?); + } + Ok(results) +} + +pub fn add_tag(path: &str, name: &str, parent_id: Option, color: &str) -> anyhow::Result { + let conn = Connection::open(path)?; + conn.execute( + "INSERT INTO tags (name, parent_id, color) VALUES (?1, ?2, ?3)", + params![name, parent_id, color], + )?; + Ok(conn.last_insert_rowid()) +} + +pub fn delete_tag(path: &str, id: i64) -> anyhow::Result<()> { + let conn = Connection::open(path)?; + // Also delete child tags + conn.execute("DELETE FROM tags WHERE parent_id = ?1", params![id])?; + conn.execute("DELETE FROM tags WHERE id = ?1", params![id])?; + Ok(()) +} + +pub fn get_events(path: &str, date: &str) -> anyhow::Result> { + let conn = Connection::open(path)?; + let mut stmt = conn.prepare("SELECT id, date, start_minute, end_minute, main_tag_id, sub_tag_id, content FROM events WHERE date = ?1")?; + let event_iter = stmt.query_map(params![date], |row| { + Ok(Event { + id: row.get(0)?, + date: row.get(1)?, + start_minute: row.get(2)?, + end_minute: row.get(3)?, + main_tag_id: row.get(4)?, + sub_tag_id: row.get(5)?, + content: row.get(6)?, + }) + })?; + + let mut results = Vec::new(); + for event in event_iter { + results.push(event?); + } + Ok(results) +} + +pub fn save_event(path: &str, event: Event) -> anyhow::Result { + let conn = Connection::open(path)?; + if event.id > 0 { + conn.execute( + "UPDATE events SET date=?1, start_minute=?2, end_minute=?3, main_tag_id=?4, sub_tag_id=?5, content=?6 WHERE id=?7", + params![event.date, event.start_minute, event.end_minute, event.main_tag_id, event.sub_tag_id, event.content, event.id], + )?; + Ok(event.id) + } else { + conn.execute( + "INSERT INTO events (date, start_minute, end_minute, main_tag_id, sub_tag_id, content) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![event.date, event.start_minute, event.end_minute, event.main_tag_id, event.sub_tag_id, event.content], + )?; + Ok(conn.last_insert_rowid()) + } +} + +pub fn delete_event(path: &str, id: i64) -> anyhow::Result<()> { + let conn = Connection::open(path)?; + conn.execute("DELETE FROM events WHERE id = ?1", params![id])?; + Ok(()) +} diff --git a/src-tauri/src/engine.rs b/src-tauri/src/engine.rs index 7d865a5..ba7a969 100644 --- a/src-tauri/src/engine.rs +++ b/src-tauri/src/engine.rs @@ -11,9 +11,60 @@ use tauri_plugin_store::StoreExt; pub struct AppState { pub is_paused: Arc, pub capture_interval_secs: std::sync::atomic::AtomicU64, + pub db_path: std::sync::Mutex>, pub toggle_menu_item: std::sync::Mutex>>, } +#[tauri::command] +pub fn update_db_path(state: tauri::State<'_, AppState>, path: String) -> Result<(), String> { + crate::db::init_db(&path).map_err(|e| e.to_string())?; + let mut db_path = state.db_path.lock().unwrap(); + *db_path = Some(path); + Ok(()) +} + +#[tauri::command] +pub fn get_tags(state: tauri::State<'_, AppState>) -> Result, String> { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::get_tags(path).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn add_tag(state: tauri::State<'_, AppState>, name: String, parent_id: Option, color: String) -> Result { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::add_tag(path, &name, parent_id, &color).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn delete_tag(state: tauri::State<'_, AppState>, id: i64) -> Result<(), String> { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::delete_tag(path, id).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn get_events(state: tauri::State<'_, AppState>, date: String) -> Result, String> { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::get_events(path, &date).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn save_event(state: tauri::State<'_, AppState>, event: crate::db::Event) -> Result { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::save_event(path, event).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn delete_event(state: tauri::State<'_, AppState>, id: i64) -> Result<(), String> { + let path = state.db_path.lock().unwrap(); + let path = path.as_ref().ok_or("Database path not set")?; + crate::db::delete_event(path, id).map_err(|e| e.to_string()) +} + #[tauri::command] pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) { state.capture_interval_secs.store(seconds, Ordering::SeqCst); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6efbc1b..65aa1fe 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,4 @@ +mod db; mod engine; mod tray; @@ -21,12 +22,20 @@ pub fn run() { engine::get_pause_state, engine::get_timeline, engine::get_image_base64, - engine::update_interval + engine::update_interval, + engine::update_db_path, + engine::get_tags, + engine::add_tag, + engine::delete_tag, + engine::get_events, + engine::save_event, + engine::delete_event ]) .setup(|app| { app.manage(engine::AppState { is_paused: Arc::new(AtomicBool::new(false)), capture_interval_secs: std::sync::atomic::AtomicU64::new(30), + db_path: std::sync::Mutex::new(None), toggle_menu_item: std::sync::Mutex::new(None), }); tray::create_tray(app.handle())?; diff --git a/src/App.vue b/src/App.vue index 4561934..50e327f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,24 +1,23 @@