Skip to main content

agentos_vfs/local/
file_block_store.rs

1use 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}