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}