agentos_vfs/local/
file_block_store.rs1use async_trait::async_trait;
2use std::fs;
3use std::path::{Path, PathBuf};
4use vfs::engine::block::BlockStore;
5use vfs::engine::error::{VfsError, VfsResult};
6use vfs::engine::types::BlockKey;
7
8#[derive(Debug, Clone)]
9pub struct FileBlockStore {
10 root: PathBuf,
11}
12
13impl FileBlockStore {
14 pub fn new(root: impl Into<PathBuf>) -> VfsResult<Self> {
15 let root = root.into();
16 fs::create_dir_all(&root)
17 .map_err(|err| VfsError::eio(format!("create block root {}: {err}", root.display())))?;
18 Ok(Self { root })
19 }
20
21 fn path_for(&self, key: &BlockKey) -> PathBuf {
22 let (prefix, suffix) = key.0.split_at(key.0.len().min(2));
23 self.root.join(prefix).join(suffix)
24 }
25
26 fn ensure_safe_key(key: &BlockKey) -> VfsResult<()> {
27 if key.0.contains('/') || key.0.contains('\\') || key.0 == "." || key.0 == ".." {
28 return Err(VfsError::einval(format!("unsafe block key: {}", key.0)));
29 }
30 Ok(())
31 }
32
33 pub fn root(&self) -> &Path {
34 &self.root
35 }
36}
37
38#[async_trait]
39impl BlockStore for FileBlockStore {
40 async fn get(&self, key: &BlockKey) -> VfsResult<Vec<u8>> {
41 Self::ensure_safe_key(key)?;
42 let path = self.path_for(key);
43 fs::read(&path).map_err(|err| {
44 if err.kind() == std::io::ErrorKind::NotFound {
45 VfsError::enoent(&key.0)
46 } else {
47 VfsError::eio(format!("read block {}: {err}", path.display()))
48 }
49 })
50 }
51
52 async fn get_range(&self, key: &BlockKey, off: u64, len: u64) -> VfsResult<Vec<u8>> {
53 let data = self.get(key).await?;
54 let start = usize::try_from(off)
55 .map_err(|_| VfsError::einval(format!("range offset is too large: {off}")))?;
56 let len = usize::try_from(len)
57 .map_err(|_| VfsError::einval(format!("range length is too large: {len}")))?;
58 if start >= data.len() {
59 return Ok(Vec::new());
60 }
61 let end = start.saturating_add(len).min(data.len());
62 Ok(data[start..end].to_vec())
63 }
64
65 async fn put(&self, key: &BlockKey, data: &[u8]) -> VfsResult<()> {
66 Self::ensure_safe_key(key)?;
67 let path = self.path_for(key);
68 if let Some(parent) = path.parent() {
69 fs::create_dir_all(parent)
70 .map_err(|err| VfsError::eio(format!("create block dir: {err}")))?;
71 }
72 fs::write(&path, data)
73 .map_err(|err| VfsError::eio(format!("write block {}: {err}", path.display())))
74 }
75
76 async fn exists(&self, key: &BlockKey) -> VfsResult<bool> {
77 Self::ensure_safe_key(key)?;
78 Ok(self.path_for(key).exists())
79 }
80
81 async fn delete_many(&self, keys: &[BlockKey]) -> VfsResult<()> {
82 let mut errors = Vec::new();
83 for key in keys {
84 if let Err(error) = Self::ensure_safe_key(key) {
85 errors.push(error.to_string());
86 continue;
87 }
88 match fs::remove_file(self.path_for(key)) {
89 Ok(()) => {}
90 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
91 Err(err) => errors.push(format!("delete block {}: {err}", key.0)),
92 }
93 }
94 if errors.is_empty() {
95 Ok(())
96 } else {
97 Err(VfsError::eio(format!(
98 "delete {} local blocks failed: {}",
99 errors.len(),
100 errors.join("; ")
101 )))
102 }
103 }
104
105 async fn copy(&self, src: &BlockKey, dst: &BlockKey) -> VfsResult<()> {
106 let data = self.get(src).await?;
107 self.put(dst, &data).await
108 }
109}