From 7700026d87329c6a21f548fcfd6359f58145ab39 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Mon, 25 Aug 2025 12:37:18 -0300 Subject: [PATCH] feat: add async item service wrapper Co-authored-by: aider (openai/andrew/openrouter/google/gemini-2.5-pro) --- PLAN.md | 2 +- src/core/async_item_service.rs | 93 ++++++++++++++++++++++++++++++++++ src/core/mod.rs | 1 + 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/core/async_item_service.rs diff --git a/PLAN.md b/PLAN.md index a86eddc..98fba42 100644 --- a/PLAN.md +++ b/PLAN.md @@ -5,7 +5,7 @@ - [x] 2. Create common data structures - [x] 3. Update database layer for batch operations - [x] 4. Create core services with clear boundaries (synchronous) -- [ ] 5. Add async wrappers for API use +- [x] 5. Add async wrappers for API use - [x] 6. Refactor CLI modes to use services (DONE) - [ ] 7. Refactor REST API to use async services - [ ] 8. Refactor MCP tools to use services diff --git a/src/core/async_item_service.rs b/src/core/async_item_service.rs new file mode 100644 index 0000000..3ee1b9e --- /dev/null +++ b/src/core/async_item_service.rs @@ -0,0 +1,93 @@ +use crate::core::error::CoreError; +use crate::core::item_service::ItemService; +use crate::core::types::{ItemWithContent, ItemWithMeta}; +use rusqlite::Connection; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::Mutex; + +/// An asynchronous wrapper around the `ItemService` for use in async contexts like the web server. +/// It uses `tokio::task::spawn_blocking` to run synchronous database and filesystem operations +/// on a dedicated thread pool, preventing them from blocking the async runtime. +#[allow(dead_code)] +pub struct AsyncItemService { + data_path: PathBuf, + db: Arc>, +} + +#[allow(dead_code)] +impl AsyncItemService { + pub fn new(data_path: PathBuf, db: Arc>) -> Self { + Self { data_path, db } + } + + pub async fn get_item(&self, id: i64) -> Result { + let data_path = self.data_path.clone(); + let conn = self.db.lock().await; + + tokio::task::spawn_blocking(move || { + let item_service = ItemService::new(data_path); + item_service.get_item(&conn, id) + }) + .await + .unwrap() // Propagate panics from spawn_blocking + } + + pub async fn get_item_content(&self, id: i64) -> Result { + let data_path = self.data_path.clone(); + let conn = self.db.lock().await; + + tokio::task::spawn_blocking(move || { + let item_service = ItemService::new(data_path); + item_service.get_item_content(&conn, id) + }) + .await + .unwrap() + } + + pub async fn find_item( + &self, + ids: Vec, + tags: Vec, + meta: HashMap, + ) -> Result { + let data_path = self.data_path.clone(); + let conn = self.db.lock().await; + + tokio::task::spawn_blocking(move || { + let item_service = ItemService::new(data_path); + item_service.find_item(&conn, &ids, &tags, &meta) + }) + .await + .unwrap() + } + + pub async fn list_items( + &self, + tags: Vec, + meta: HashMap, + ) -> Result, CoreError> { + let data_path = self.data_path.clone(); + let conn = self.db.lock().await; + + tokio::task::spawn_blocking(move || { + let item_service = ItemService::new(data_path); + item_service.list_items(&conn, &tags, &meta) + }) + .await + .unwrap() + } + + pub async fn delete_item(&self, id: i64) -> Result<(), CoreError> { + let data_path = self.data_path.clone(); + let mut conn = self.db.lock().await; + + tokio::task::spawn_blocking(move || { + let item_service = ItemService::new(data_path); + item_service.delete_item(&mut conn, id) + }) + .await + .unwrap() + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 9bedb73..7874eb0 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,4 @@ +pub mod async_item_service; pub mod compression_service; pub mod error; pub mod item_service;