vectorless/client/
workspace.rs1use std::sync::Arc;
27
28use tracing::{debug, info, warn};
29
30use crate::error::Result;
31use crate::storage::{PersistedDocument, Workspace};
32
33use super::events::{EventEmitter, WorkspaceEvent};
34use super::types::DocumentInfo;
35
36#[derive(Clone)]
46pub struct WorkspaceClient {
47 workspace: Arc<Workspace>,
49
50 events: EventEmitter,
52
53 config: WorkspaceClientConfig,
55}
56
57#[derive(Debug, Clone)]
59pub struct WorkspaceClientConfig {
60 pub auto_save_interval: Option<u64>,
62
63 pub verbose: bool,
65}
66
67impl Default for WorkspaceClientConfig {
68 fn default() -> Self {
69 Self {
70 auto_save_interval: None,
71 verbose: false,
72 }
73 }
74}
75
76impl WorkspaceClient {
77 pub async fn new(workspace: Workspace) -> Self {
79 Self {
80 workspace: Arc::new(workspace),
81 events: EventEmitter::new(),
82 config: WorkspaceClientConfig::default(),
83 }
84 }
85
86 pub fn with_events(mut self, events: EventEmitter) -> Self {
88 self.events = events;
89 self
90 }
91
92 pub fn with_config(mut self, config: WorkspaceClientConfig) -> Self {
94 self.config = config;
95 self
96 }
97
98 pub(crate) fn from_arc(workspace: Arc<Workspace>, events: EventEmitter) -> Self {
100 Self {
101 workspace,
102 events,
103 config: WorkspaceClientConfig::default(),
104 }
105 }
106
107 pub async fn save(&self, doc: &PersistedDocument) -> Result<()> {
113 let doc_id = doc.meta.id.clone();
114
115 self.workspace.add(doc).await?;
116
117 info!("Saved document: {}", doc_id);
118 self.events.emit_workspace(WorkspaceEvent::Saved { doc_id });
119
120 Ok(())
121 }
122
123 pub async fn load(&self, doc_id: &str) -> Result<Option<PersistedDocument>> {
131 if !self.workspace.contains(doc_id).await {
132 return Ok(None);
133 }
134
135 let doc = self.workspace.load_and_cache(doc_id).await?;
136 let cache_hit = doc.is_some();
137
138 if let Some(ref doc) = doc {
139 debug!("Loaded document: {} (cache={})", doc_id, cache_hit);
140 }
141
142 self.events.emit_workspace(WorkspaceEvent::Loaded {
143 doc_id: doc_id.to_string(),
144 cache_hit,
145 });
146
147 Ok(doc)
148 }
149
150 pub async fn remove(&self, doc_id: &str) -> Result<bool> {
158 let removed = self.workspace.remove(doc_id).await?;
159
160 if removed {
161 info!("Removed document: {}", doc_id);
162 self.events.emit_workspace(WorkspaceEvent::Removed {
163 doc_id: doc_id.to_string(),
164 });
165 }
166
167 Ok(removed)
168 }
169
170 pub async fn exists(&self, doc_id: &str) -> Result<bool> {
176 Ok(self.workspace.contains(doc_id).await)
177 }
178
179 pub async fn list(&self) -> Result<Vec<DocumentInfo>> {
185 let doc_ids = self.workspace.list_documents().await;
186 let mut result = Vec::with_capacity(doc_ids.len());
187
188 for id in &doc_ids {
189 if let Some(meta) = self.workspace.get_meta(id).await {
190 result.push(DocumentInfo {
191 id: meta.id,
192 name: meta.doc_name,
193 format: meta.doc_type,
194 description: meta.doc_description,
195 page_count: meta.page_count,
196 line_count: meta.line_count,
197 });
198 }
199 }
200
201 Ok(result)
202 }
203
204 pub async fn get_document_info(&self, doc_id: &str) -> Result<Option<DocumentInfo>> {
210 Ok(self
211 .workspace
212 .get_meta(doc_id)
213 .await
214 .map(|meta| DocumentInfo {
215 id: meta.id,
216 name: meta.doc_name,
217 format: meta.doc_type,
218 description: meta.doc_description,
219 page_count: meta.page_count,
220 line_count: meta.line_count,
221 }))
222 }
223
224 pub async fn batch_remove(&self, doc_ids: &[&str]) -> Result<usize> {
232 let mut removed = 0;
233
234 for doc_id in doc_ids {
235 if self.workspace.remove(doc_id).await? {
236 removed += 1;
237 self.events.emit_workspace(WorkspaceEvent::Removed {
238 doc_id: doc_id.to_string(),
239 });
240 }
241 }
242
243 if removed > 0 {
244 info!("Batch removed {} documents", removed);
245 }
246
247 Ok(removed)
248 }
249
250 pub async fn clear(&self) -> Result<usize> {
258 let doc_ids = self.workspace.list_documents().await;
259 let count = doc_ids.len();
260
261 for doc_id in &doc_ids {
262 let _ = self.workspace.remove(doc_id).await;
263 }
264
265 if count > 0 {
266 info!("Cleared workspace: {} documents removed", count);
267 self.events
268 .emit_workspace(WorkspaceEvent::Cleared { count });
269 }
270
271 Ok(count)
272 }
273
274 pub async fn stats(&self) -> Result<WorkspaceStats> {
276 Ok(WorkspaceStats {
277 document_count: self.workspace.len().await,
278 })
279 }
280
281 pub async fn len(&self) -> usize {
283 self.workspace.len().await
284 }
285
286 pub async fn is_empty(&self) -> bool {
288 self.workspace.is_empty().await
289 }
290
291 pub(crate) fn inner(&self) -> Arc<Workspace> {
293 Arc::clone(&self.workspace)
294 }
295}
296
297#[derive(Debug, Clone)]
299pub struct WorkspaceStats {
300 pub document_count: usize,
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::storage::backend::MemoryBackend;
308 use std::sync::Arc as StdArc;
309
310 #[tokio::test]
311 async fn test_workspace_client_creation() {
312 let backend = StdArc::new(MemoryBackend::new());
313 let workspace = Workspace::with_backend(backend).await.unwrap();
314 let client = WorkspaceClient::new(workspace).await;
315 assert!(client.is_empty().await);
316 }
317
318 #[tokio::test]
319 async fn test_workspace_stats() {
320 let backend = StdArc::new(MemoryBackend::new());
321 let workspace = Workspace::with_backend(backend).await.unwrap();
322 let client = WorkspaceClient::new(workspace).await;
323
324 let stats = client.stats().await.unwrap();
325 assert_eq!(stats.document_count, 0);
326 }
327}