Skip to main content

vv_agent/workspace/
memory.rs

1use std::any::Any;
2use std::collections::{BTreeMap, BTreeSet};
3use std::sync::{Arc, Mutex};
4
5use super::{
6    current_utc_isoformat, glob_match, insert_parent_dirs, normalize_workspace_path,
7    normalized_glob_pattern, not_found, suffix_with_dot, FileInfo, WorkspaceBackend,
8};
9
10#[derive(Debug, Clone)]
11pub struct MemoryWorkspaceBackend {
12    files: Arc<Mutex<BTreeMap<String, Vec<u8>>>>,
13    dirs: Arc<Mutex<BTreeSet<String>>>,
14}
15
16impl Default for MemoryWorkspaceBackend {
17    fn default() -> Self {
18        let mut dirs = BTreeSet::new();
19        dirs.insert(String::new());
20        Self {
21            files: Arc::new(Mutex::new(BTreeMap::new())),
22            dirs: Arc::new(Mutex::new(dirs)),
23        }
24    }
25}
26
27impl WorkspaceBackend for MemoryWorkspaceBackend {
28    fn as_any(&self) -> &dyn Any {
29        self
30    }
31
32    fn list_files(&self, base: &str, glob: &str) -> std::io::Result<Vec<String>> {
33        let base = normalize_workspace_path(base);
34        let glob = normalized_glob_pattern(glob);
35        let pattern = if base.is_empty() {
36            glob
37        } else {
38            format!("{base}/{glob}")
39        };
40        let files = self.files.lock().expect("memory workspace poisoned");
41        let mut matches = files
42            .keys()
43            .filter(|path| glob_match(path, &pattern))
44            .cloned()
45            .collect::<Vec<_>>();
46        matches.sort();
47        Ok(matches)
48    }
49
50    fn read_text(&self, path: &str) -> std::io::Result<String> {
51        let bytes = self.read_bytes(path)?;
52        Ok(String::from_utf8_lossy(&bytes).to_string())
53    }
54
55    fn read_bytes(&self, path: &str) -> std::io::Result<Vec<u8>> {
56        let key = normalize_workspace_path(path);
57        let files = self.files.lock().expect("memory workspace poisoned");
58        files.get(&key).cloned().ok_or_else(|| not_found(path))
59    }
60
61    fn write_text(&self, path: &str, content: &str, append: bool) -> std::io::Result<usize> {
62        let key = normalize_workspace_path(path);
63        let mut files = self.files.lock().expect("memory workspace poisoned");
64        let entry = files.entry(key.clone()).or_default();
65        if append {
66            entry.extend_from_slice(content.as_bytes());
67        } else {
68            *entry = content.as_bytes().to_vec();
69        }
70        drop(files);
71        self.ensure_parent_dirs(&key);
72        Ok(content.len())
73    }
74
75    fn file_info(&self, path: &str) -> std::io::Result<Option<FileInfo>> {
76        let key = normalize_workspace_path(path);
77        let files = self.files.lock().expect("memory workspace poisoned");
78        if let Some(content) = files.get(&key) {
79            return Ok(Some(FileInfo {
80                path: key,
81                is_file: true,
82                is_dir: false,
83                size: content.len() as u64,
84                modified_at: current_utc_isoformat(),
85                suffix: suffix_with_dot(path),
86            }));
87        }
88        drop(files);
89        let dirs = self.dirs.lock().expect("memory workspace poisoned");
90        Ok(dirs.get(&key).map(|path| FileInfo {
91            path: if path.is_empty() {
92                ".".to_string()
93            } else {
94                path.clone()
95            },
96            is_file: false,
97            is_dir: true,
98            size: 0,
99            modified_at: current_utc_isoformat(),
100            suffix: String::new(),
101        }))
102    }
103
104    fn exists(&self, path: &str) -> bool {
105        let key = normalize_workspace_path(path);
106        self.files
107            .lock()
108            .expect("memory workspace poisoned")
109            .contains_key(&key)
110            || self
111                .dirs
112                .lock()
113                .expect("memory workspace poisoned")
114                .contains(&key)
115    }
116
117    fn is_file(&self, path: &str) -> bool {
118        let key = normalize_workspace_path(path);
119        self.files
120            .lock()
121            .expect("memory workspace poisoned")
122            .contains_key(&key)
123    }
124
125    fn mkdir(&self, path: &str) -> std::io::Result<()> {
126        let key = normalize_workspace_path(path);
127        let mut dirs = self.dirs.lock().expect("memory workspace poisoned");
128        dirs.insert(key.clone());
129        insert_parent_dirs(&mut dirs, &key);
130        Ok(())
131    }
132}
133
134impl MemoryWorkspaceBackend {
135    fn ensure_parent_dirs(&self, key: &str) {
136        let mut dirs = self.dirs.lock().expect("memory workspace poisoned");
137        insert_parent_dirs(&mut dirs, key);
138    }
139}