freighter_storage/
fs.rs

1use async_trait::async_trait;
2use freighter_api_types::storage::{
3    Bytes, Metadata, MetadataStorageProvider, StorageError, StorageResult,
4};
5use std::io;
6use std::io::{Seek, Write};
7use std::path::PathBuf;
8use tempfile::NamedTempFile;
9
10pub struct FsStorageProvider {
11    root: PathBuf,
12}
13
14impl FsStorageProvider {
15    pub fn new(root: PathBuf) -> StorageResult<Self> {
16        std::fs::create_dir_all(&root)?;
17        Ok(Self { root })
18    }
19
20    fn abs_path(&self, path: &str) -> StorageResult<PathBuf> {
21        let path = self.root.join(path);
22        if !path.starts_with(&self.root) {
23            return Err(StorageError::ServiceError(
24                io::Error::from(io::ErrorKind::InvalidInput).into(),
25            ));
26        }
27        Ok(path)
28    }
29}
30
31#[async_trait]
32impl MetadataStorageProvider for FsStorageProvider {
33    async fn pull_file(&self, path: &str) -> StorageResult<Bytes> {
34        let data = std::fs::read(self.root.join(path))?;
35        Ok(data.into())
36    }
37
38    async fn put_file(&self, path: &str, file_bytes: Bytes, _meta: Metadata) -> StorageResult<()> {
39        let path = self.abs_path(path)?;
40        let parent = path.parent().unwrap();
41        if !parent.exists() {
42            std::fs::create_dir_all(parent)?;
43        }
44        let mut tmp = NamedTempFile::new_in(parent)?;
45        tmp.write_all(&file_bytes)?;
46        tmp.persist(path).map_err(|e| StorageError::ServiceError(e.into()))?;
47        Ok(())
48    }
49
50    async fn create_or_append_file(&self, path: &str, file_bytes: Bytes, _meta: Metadata) -> StorageResult<()> {
51        let path = self.abs_path(path)?;
52        let parent = path.parent().unwrap();
53        if !parent.exists() {
54            std::fs::create_dir_all(parent)?;
55        }
56        let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?;
57        file.seek(io::SeekFrom::End(0))?;
58        file.write_all(&file_bytes)?;
59        Ok(())
60    }
61
62    async fn delete_file(&self, path: &str) -> StorageResult<()> {
63        let path = self.abs_path(path)?;
64        std::fs::remove_file(path)?;
65        Ok(())
66    }
67
68    async fn healthcheck(&self) -> anyhow::Result<()> {
69        if self.root.is_dir() {
70            Ok(())
71        } else {
72            anyhow::bail!("root not a dir")
73        }
74    }
75}