From 26e825bb192a2e79cdc15b283d265c27d13494dd Mon Sep 17 00:00:00 2001 From: Vixalie Date: Wed, 5 Mar 2025 21:55:19 +0800 Subject: [PATCH] add patterns storage functions. --- src-tauri/src/cmd/mod.rs | 17 +++++++- src-tauri/src/config_db.rs | 89 +++++++++++++++++++++++++++++++++++--- src-tauri/src/errors.rs | 2 + src-tauri/src/lib.rs | 3 +- src-tauri/src/state.rs | 8 ++-- 5 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/cmd/mod.rs b/src-tauri/src/cmd/mod.rs index 12f1b8b..07c4e1f 100644 --- a/src-tauri/src/cmd/mod.rs +++ b/src-tauri/src/cmd/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::VecDeque, sync::Arc}; use btleplug::{ api::{Central as _, Peripheral as _}, @@ -9,6 +9,7 @@ use tauri::{async_runtime::RwLock, AppHandle, Emitter, State}; use crate::{ bluetooth, errors, + pattern::Pattern, state::{self as global, AppState}, }; @@ -128,3 +129,17 @@ pub async fn stop_scan_devices(app_handle: AppHandle) -> Result<(), errors::AppE bluetooth::stop_scan(Arc::new(app_handle)).await?; Ok(()) } + +#[tauri::command] +pub async fn list_patterns( + app_state: State<'_, Arc>>, + keyword: Option, +) -> Result, errors::AppError> { + let state = app_state.read().await; + let patterns = state + .db + .get_patterns(keyword) + .await + .map_err(|e| errors::AppError::StorageFailure(e.to_string()))?; + Ok(patterns) +} diff --git a/src-tauri/src/config_db.rs b/src-tauri/src/config_db.rs index aed7af6..8d254f7 100644 --- a/src-tauri/src/config_db.rs +++ b/src-tauri/src/config_db.rs @@ -1,6 +1,12 @@ -use std::{fs::DirBuilder, path::PathBuf}; +use std::{collections::VecDeque, fs::DirBuilder, path::PathBuf, sync::Arc}; use tauri::{path::PathResolver, AppHandle, Manager, Runtime}; +use tokio::sync::Mutex; +use uuid::Uuid; + +use crate::pattern::Pattern; + +static PATTERN_KEY_PREFIX: &[u8] = b"PATTERN:"; fn ensure_dir(resolver: &PathResolver) -> anyhow::Result { let config_dir = resolver @@ -16,8 +22,81 @@ fn ensure_dir(resolver: &PathResolver) -> anyhow::Result Ok(config_dir) } -pub fn open_config_db(app_handle: &AppHandle) -> anyhow::Result { - let config_dir = ensure_dir(app_handle.path())?; - let db_path = config_dir.join("conf.db"); - sled::open(&db_path).map_err(|e| anyhow::anyhow!("Unable to open config database: {}", e)) +pub struct ConfigDb(Arc>); + +impl ConfigDb { + pub fn open(app_handle: &AppHandle) -> anyhow::Result { + let config_dir = ensure_dir(app_handle.path())?; + let db_path = config_dir.join("conf.db"); + let db = sled::open(&db_path) + .map_err(|e| anyhow::anyhow!("Unable to open config database: {}", e))?; + Ok(Self(Arc::new(Mutex::new(db)))) + } + + pub async fn store_pattern(&self, pattern: &Pattern) -> anyhow::Result<()> { + let db = self.0.lock().await; + let key = PATTERN_KEY_PREFIX + .iter() + .chain(pattern.id.as_bytes()) + .cloned() + .collect::>(); + db.insert(key, pattern) + .map_err(|e| anyhow::anyhow!("Unable to store pattern: {}", e))?; + db.flush_async() + .await + .map_err(|e| anyhow::anyhow!("Unable to save db: {}", e))?; + Ok(()) + } + + pub async fn get_patterns(&self, keyword: Option) -> anyhow::Result> { + let db = self.0.lock().await; + let patterns = db.scan_prefix(PATTERN_KEY_PREFIX); + let mut result: VecDeque = VecDeque::new(); + for entry in patterns { + if let Ok((_, pattern)) = entry { + let pattern = Into::::into(pattern); + if let Some(keyword) = &keyword { + if !pattern.name.contains(keyword) { + continue; + } + } + let index = + result.binary_search_by(|item| item.created_at.cmp(&pattern.created_at)); + match index { + Ok(index) => result.insert(index + 1, pattern), + Err(index) => result.insert(index, pattern), + }; + } + } + Ok(result) + } + + pub async fn get_pattern(&self, pattern_id: &Uuid) -> anyhow::Result> { + let db = self.0.lock().await; + let key = PATTERN_KEY_PREFIX + .iter() + .chain(pattern_id.as_bytes()) + .cloned() + .collect::>(); + let pattern = db + .get(key) + .map_err(|e| anyhow::anyhow!("Unable to get pattern: {}", e))? + .map(|p| Into::::into(p)); + Ok(pattern) + } + + pub async fn remove_pattern(&self, pattern_id: &Uuid) -> anyhow::Result<()> { + let db = self.0.lock().await; + let key = PATTERN_KEY_PREFIX + .iter() + .chain(pattern_id.as_bytes()) + .cloned() + .collect::>(); + db.remove(key) + .map_err(|e| anyhow::anyhow!("Unable to remove pattern: {}", e))?; + db.flush_async() + .await + .map_err(|e| anyhow::anyhow!("Unable to save db: {}", e))?; + Ok(()) + } } diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index e4e49b5..6cdaac3 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -23,4 +23,6 @@ pub enum AppError { UnableToRetrievePeripheralState, #[error("Unable to retrieve peripheral services")] UnableToRetrievePeripheralServices, + #[error("Visit data storage failed: {0}")] + StorageFailure(String), } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 75c22c2..89b55be 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -64,7 +64,8 @@ pub fn run() { cmd::channel_b_state, cmd::activate_central_adapter, cmd::start_scan_devices, - cmd::stop_scan_devices + cmd::stop_scan_devices, + cmd::list_patterns ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 75c1049..74d7168 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -10,10 +10,10 @@ use tauri::{ }; use uuid::Uuid; -use crate::config_db; +use crate::config_db::ConfigDb; pub struct AppState { - pub db: Arc>, + pub db: ConfigDb, pub central_event_handler: Arc>>>, } @@ -32,10 +32,10 @@ static CONNECTED_PERIPHERAL: LazyLock>>> = impl AppState { pub async fn new(app: &AppHandle) -> anyhow::Result { - let db = config_db::open_config_db(app)?; + let db = ConfigDb::open(app)?; Ok(Self { - db: Arc::new(Mutex::new(db)), + db, central_event_handler: Arc::new(Mutex::new(None)), }) }