add patterns storage functions.

This commit is contained in:
Vixalie 2025-03-05 21:55:19 +08:00
parent a1b5859cd6
commit 26e825bb19
5 changed files with 108 additions and 11 deletions

View File

@ -1,4 +1,4 @@
use std::sync::Arc; use std::{collections::VecDeque, sync::Arc};
use btleplug::{ use btleplug::{
api::{Central as _, Peripheral as _}, api::{Central as _, Peripheral as _},
@ -9,6 +9,7 @@ use tauri::{async_runtime::RwLock, AppHandle, Emitter, State};
use crate::{ use crate::{
bluetooth, errors, bluetooth, errors,
pattern::Pattern,
state::{self as global, AppState}, 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?; bluetooth::stop_scan(Arc::new(app_handle)).await?;
Ok(()) Ok(())
} }
#[tauri::command]
pub async fn list_patterns(
app_state: State<'_, Arc<RwLock<AppState>>>,
keyword: Option<String>,
) -> Result<VecDeque<Pattern>, 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)
}

View File

@ -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 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<R: Runtime>(resolver: &PathResolver<R>) -> anyhow::Result<PathBuf> { fn ensure_dir<R: Runtime>(resolver: &PathResolver<R>) -> anyhow::Result<PathBuf> {
let config_dir = resolver let config_dir = resolver
@ -16,8 +22,81 @@ fn ensure_dir<R: Runtime>(resolver: &PathResolver<R>) -> anyhow::Result<PathBuf>
Ok(config_dir) Ok(config_dir)
} }
pub fn open_config_db<R: Runtime>(app_handle: &AppHandle<R>) -> anyhow::Result<sled::Db> { pub struct ConfigDb(Arc<Mutex<sled::Db>>);
let config_dir = ensure_dir(app_handle.path())?;
let db_path = config_dir.join("conf.db"); impl ConfigDb {
sled::open(&db_path).map_err(|e| anyhow::anyhow!("Unable to open config database: {}", e)) pub fn open<R: Runtime>(app_handle: &AppHandle<R>) -> anyhow::Result<Self> {
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::<Vec<u8>>();
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<String>) -> anyhow::Result<VecDeque<Pattern>> {
let db = self.0.lock().await;
let patterns = db.scan_prefix(PATTERN_KEY_PREFIX);
let mut result: VecDeque<Pattern> = VecDeque::new();
for entry in patterns {
if let Ok((_, pattern)) = entry {
let pattern = Into::<Pattern>::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<Option<Pattern>> {
let db = self.0.lock().await;
let key = PATTERN_KEY_PREFIX
.iter()
.chain(pattern_id.as_bytes())
.cloned()
.collect::<Vec<u8>>();
let pattern = db
.get(key)
.map_err(|e| anyhow::anyhow!("Unable to get pattern: {}", e))?
.map(|p| Into::<Pattern>::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::<Vec<u8>>();
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(())
}
} }

View File

@ -23,4 +23,6 @@ pub enum AppError {
UnableToRetrievePeripheralState, UnableToRetrievePeripheralState,
#[error("Unable to retrieve peripheral services")] #[error("Unable to retrieve peripheral services")]
UnableToRetrievePeripheralServices, UnableToRetrievePeripheralServices,
#[error("Visit data storage failed: {0}")]
StorageFailure(String),
} }

View File

@ -64,7 +64,8 @@ pub fn run() {
cmd::channel_b_state, cmd::channel_b_state,
cmd::activate_central_adapter, cmd::activate_central_adapter,
cmd::start_scan_devices, cmd::start_scan_devices,
cmd::stop_scan_devices cmd::stop_scan_devices,
cmd::list_patterns
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -10,10 +10,10 @@ use tauri::{
}; };
use uuid::Uuid; use uuid::Uuid;
use crate::config_db; use crate::config_db::ConfigDb;
pub struct AppState { pub struct AppState {
pub db: Arc<Mutex<sled::Db>>, pub db: ConfigDb,
pub central_event_handler: Arc<Mutex<Option<JoinHandle<()>>>>, pub central_event_handler: Arc<Mutex<Option<JoinHandle<()>>>>,
} }
@ -32,10 +32,10 @@ static CONNECTED_PERIPHERAL: LazyLock<Arc<Mutex<Option<PeripheralId>>>> =
impl AppState { impl AppState {
pub async fn new<R: Runtime>(app: &AppHandle<R>) -> anyhow::Result<Self> { pub async fn new<R: Runtime>(app: &AppHandle<R>) -> anyhow::Result<Self> {
let db = config_db::open_config_db(app)?; let db = ConfigDb::open(app)?;
Ok(Self { Ok(Self {
db: Arc::new(Mutex::new(db)), db,
central_event_handler: Arc::new(Mutex::new(None)), central_event_handler: Arc::new(Mutex::new(None)),
}) })
} }