doido_storage/providers/
disk.rs1use crate::error::StorageError;
10use crate::service::Service;
11use doido_core::Result;
12use std::path::{Path, PathBuf};
13
14pub struct DiskService {
16 name: String,
17 root: PathBuf,
18 public: bool,
19}
20
21impl DiskService {
22 pub fn new(name: impl Into<String>, root: impl Into<PathBuf>) -> Self {
24 Self {
25 name: name.into(),
26 root: root.into(),
27 public: false,
28 }
29 }
30
31 pub fn public(mut self, public: bool) -> Self {
33 self.public = public;
34 self
35 }
36
37 fn path_for(&self, key: &str) -> Result<PathBuf> {
39 if key.is_empty() || key.contains('/') || key.contains('\\') || key.contains("..") {
40 return Err(StorageError::Backend(format!("invalid storage key {key:?}")).into());
41 }
42 let (a, b) = shards(key);
43 Ok(self.root.join(a).join(b).join(key))
44 }
45}
46
47fn shards(key: &str) -> (String, String) {
49 let padded = format!("{key:_<4}");
50 (padded[0..2].to_string(), padded[2..4].to_string())
51}
52
53#[async_trait::async_trait]
54impl Service for DiskService {
55 fn name(&self) -> &str {
56 &self.name
57 }
58
59 fn public(&self) -> bool {
60 self.public
61 }
62
63 async fn upload(&self, key: &str, data: Vec<u8>, _content_type: Option<&str>) -> Result<()> {
64 let path = self.path_for(key)?;
65 if let Some(parent) = path.parent() {
66 tokio::fs::create_dir_all(parent)
67 .await
68 .map_err(StorageError::from)?;
69 }
70 tokio::fs::write(&path, data)
71 .await
72 .map_err(StorageError::from)?;
73 Ok(())
74 }
75
76 async fn download(&self, key: &str) -> Result<Vec<u8>> {
77 let path = self.path_for(key)?;
78 match tokio::fs::read(&path).await {
79 Ok(bytes) => Ok(bytes),
80 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
81 Err(StorageError::NotFound(key.to_string()).into())
82 }
83 Err(e) => Err(StorageError::from(e).into()),
84 }
85 }
86
87 async fn delete(&self, key: &str) -> Result<()> {
88 let path = self.path_for(key)?;
89 match tokio::fs::remove_file(&path).await {
90 Ok(()) => Ok(()),
91 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
93 Err(e) => Err(StorageError::from(e).into()),
94 }
95 }
96
97 async fn exists(&self, key: &str) -> Result<bool> {
98 let path = self.path_for(key)?;
99 Ok(tokio::fs::try_exists(&path).await.unwrap_or(false))
100 }
101
102 async fn size(&self, key: &str) -> Result<u64> {
103 let path = self.path_for(key)?;
104 let meta = tokio::fs::metadata(&path).await.map_err(|e| {
105 if e.kind() == std::io::ErrorKind::NotFound {
106 StorageError::NotFound(key.to_string())
107 } else {
108 StorageError::from(e)
109 }
110 })?;
111 Ok(meta.len())
112 }
113}
114
115impl DiskService {
117 pub fn root(&self) -> &Path {
118 &self.root
119 }
120}