1use super::backend::Storage;
4use super::errors::{StorageError, StorageResult};
5use super::file::{FileMetadata, StoredFile};
6use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10
11#[derive(Clone, Debug)]
13struct MemoryFile {
14 content: Vec<u8>,
15 created_at: DateTime<Utc>,
16 modified_at: DateTime<Utc>,
17 accessed_at: DateTime<Utc>,
18}
19
20impl MemoryFile {
21 fn new(content: Vec<u8>) -> Self {
22 let now = Utc::now();
23 Self {
24 content,
25 created_at: now,
26 modified_at: now,
27 accessed_at: now,
28 }
29 }
30
31 fn update(&mut self, content: Vec<u8>) {
32 self.content = content;
33 self.modified_at = Utc::now();
34 }
35
36 fn access(&mut self) {
37 self.accessed_at = Utc::now();
38 }
39}
40
41#[derive(Clone)]
43pub struct InMemoryStorage {
44 files: Arc<RwLock<HashMap<String, MemoryFile>>>,
45 base_url: String,
46 location: String,
47 file_permissions_mode: Option<u32>,
48 directory_permissions_mode: Option<u32>,
49}
50
51impl InMemoryStorage {
52 pub fn new(location: impl Into<String>, base_url: impl Into<String>) -> Self {
64 Self {
65 files: Arc::new(RwLock::new(HashMap::new())),
66 base_url: base_url.into(),
67 location: location.into(),
68 file_permissions_mode: None,
69 directory_permissions_mode: None,
70 }
71 }
72 pub fn with_permissions(mut self, file_mode: Option<u32>, dir_mode: Option<u32>) -> Self {
85 self.file_permissions_mode = file_mode;
86 self.directory_permissions_mode = dir_mode;
87 self
88 }
89 pub fn base_location(&self) -> &str {
100 &self.location
101 }
102 pub fn base_url(&self) -> &str {
113 &self.base_url
114 }
115 pub fn file_permissions_mode(&self) -> Option<u32> {
130 self.file_permissions_mode
131 }
132 pub fn directory_permissions_mode(&self) -> Option<u32> {
147 self.directory_permissions_mode
148 }
149 pub fn deconstruct(&self) -> (&str, (), HashMap<String, String>) {
166 let mut kwargs = HashMap::new();
167 kwargs.insert("location".to_string(), self.location.clone());
168 kwargs.insert("base_url".to_string(), self.base_url.clone());
169 if let Some(mode) = self.file_permissions_mode {
170 kwargs.insert("file_permissions_mode".to_string(), format!("0o{:o}", mode));
171 }
172 if let Some(mode) = self.directory_permissions_mode {
173 kwargs.insert(
174 "directory_permissions_mode".to_string(),
175 format!("0o{:o}", mode),
176 );
177 }
178 ("reinhardt_storage.InMemoryStorage", (), kwargs)
179 }
180}
181
182#[async_trait]
183impl Storage for InMemoryStorage {
184 async fn save(&self, path: &str, content: &[u8]) -> StorageResult<FileMetadata> {
185 let mut files = self.files.write().unwrap_or_else(|e| e.into_inner());
186
187 if let Some(existing) = files.get_mut(path) {
188 existing.update(content.to_vec());
189 } else {
190 let file = MemoryFile::new(content.to_vec());
191 files.insert(path.to_string(), file);
192 }
193
194 Ok(FileMetadata::new(path.to_string(), content.len() as u64))
195 }
196
197 async fn read(&self, path: &str) -> StorageResult<StoredFile> {
198 let mut files = self.files.write().unwrap_or_else(|e| e.into_inner());
199
200 let file = files
201 .get_mut(path)
202 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
203
204 file.access();
205
206 let metadata = FileMetadata::new(path.to_string(), file.content.len() as u64);
207 Ok(StoredFile::new(metadata, file.content.clone()))
208 }
209
210 async fn delete(&self, path: &str) -> StorageResult<()> {
211 let mut files = self.files.write().unwrap_or_else(|e| e.into_inner());
212
213 if path.ends_with('/') || !files.contains_key(path) {
215 let prefix = path.trim_end_matches('/');
216 let to_remove: Vec<String> = files
217 .keys()
218 .filter(|k| k.starts_with(&format!("{}/", prefix)))
219 .cloned()
220 .collect();
221
222 for key in to_remove {
223 files.remove(&key);
224 }
225 } else {
226 files
227 .remove(path)
228 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
229 }
230
231 Ok(())
232 }
233
234 async fn exists(&self, path: &str) -> StorageResult<bool> {
235 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
236
237 if files.contains_key(path) {
239 return Ok(true);
240 }
241
242 let prefix = format!("{}/", path.trim_end_matches('/'));
244 Ok(files.keys().any(|k| k.starts_with(&prefix)))
245 }
246
247 async fn metadata(&self, path: &str) -> StorageResult<FileMetadata> {
248 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
249
250 let file = files
251 .get(path)
252 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
253
254 Ok(FileMetadata::new(
255 path.to_string(),
256 file.content.len() as u64,
257 ))
258 }
259
260 async fn list(&self, path: &str) -> StorageResult<Vec<FileMetadata>> {
261 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
262
263 let prefix = if path.is_empty() {
264 String::new()
265 } else {
266 format!("{}/", path.trim_end_matches('/'))
267 };
268
269 let mut results = Vec::new();
270 for (key, file) in files.iter() {
271 if path.is_empty() {
272 if !key.contains('/') {
274 results.push(FileMetadata::new(key.clone(), file.content.len() as u64));
275 }
276 } else if key.starts_with(&prefix) {
277 let relative = &key[prefix.len()..];
278 if !relative.contains('/') {
280 results.push(FileMetadata::new(key.clone(), file.content.len() as u64));
281 }
282 }
283 }
284
285 Ok(results)
286 }
287
288 fn url(&self, path: &str) -> String {
289 if path.is_empty() || path == "." {
290 return format!("{}/", self.base_url.trim_end_matches('/'));
291 }
292 format!(
293 "{}/{}",
294 self.base_url.trim_end_matches('/'),
295 path.trim_start_matches('/')
296 )
297 }
298
299 fn path(&self, name: &str) -> String {
300 name.to_string()
301 }
302
303 async fn get_accessed_time(&self, path: &str) -> StorageResult<DateTime<Utc>> {
304 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
305
306 let file = files
307 .get(path)
308 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
309
310 Ok(file.accessed_at)
311 }
312
313 async fn get_created_time(&self, path: &str) -> StorageResult<DateTime<Utc>> {
314 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
315
316 let file = files
317 .get(path)
318 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
319
320 Ok(file.created_at)
321 }
322
323 async fn get_modified_time(&self, path: &str) -> StorageResult<DateTime<Utc>> {
324 let files = self.files.read().unwrap_or_else(|e| e.into_inner());
325
326 let file = files
327 .get(path)
328 .ok_or_else(|| StorageError::NotFound(path.to_string()))?;
329
330 Ok(file.modified_at)
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[tokio::test]
339 async fn test_inmemory_write_and_read() {
340 let storage = InMemoryStorage::new("memory_root", "http://localhost/media");
341
342 storage.save("file.txt", b"hello").await.unwrap();
344 let file = storage.read("file.txt").await.unwrap();
345 assert_eq!(file.content, b"hello");
346
347 storage.save("file.dat", b"hello").await.unwrap();
349 let file = storage.read("file.dat").await.unwrap();
350 assert_eq!(file.content, b"hello");
351 }
352
353 #[tokio::test]
354 async fn test_inmemory_str_bytes_conversion() {
355 let storage = InMemoryStorage::new("memory_root", "http://localhost/media");
356
357 storage.save("file.txt", b"hello").await.unwrap();
359 let file = storage.read("file.txt").await.unwrap();
360 assert_eq!(file.content, b"hello");
361
362 storage.save("file.dat", b"hello").await.unwrap();
363 let file = storage.read("file.dat").await.unwrap();
364 assert_eq!(file.content, b"hello");
365 }
366
367 #[tokio::test]
368 async fn test_inmemory_url_generation() {
369 let storage = InMemoryStorage::new("memory_root", "http://localhost/media");
370 assert_eq!(storage.url("test.txt"), "http://localhost/media/test.txt");
371
372 let storage2 = InMemoryStorage::new("memory_root", "http://localhost/media/");
374 assert_eq!(storage2.url("test.txt"), "http://localhost/media/test.txt");
375 }
376
377 #[tokio::test]
378 async fn test_inmemory_url_with_none_filename() {
379 let storage = InMemoryStorage::new("memory_root", "/test_media_url/");
380 assert_eq!(storage.url(""), "/test_media_url/");
381 }
382
383 #[tokio::test]
384 async fn test_inmemory_deconstruction() {
385 let storage = InMemoryStorage::new("memory_root", "http://localhost/media");
386 let (path, args, kwargs) = storage.deconstruct();
387
388 assert_eq!(path, "reinhardt_storage.InMemoryStorage");
389 assert_eq!(args, ());
390 assert_eq!(kwargs.get("location").unwrap(), "memory_root");
391 assert_eq!(kwargs.get("base_url").unwrap(), "http://localhost/media");
392
393 let storage_with_perms = InMemoryStorage::new("custom_path", "http://example.com/")
395 .with_permissions(Some(0o755), Some(0o600));
396 let (_, _, kwargs) = storage_with_perms.deconstruct();
397
398 assert_eq!(kwargs.get("location").unwrap(), "custom_path");
399 assert_eq!(kwargs.get("base_url").unwrap(), "http://example.com/");
400 assert_eq!(kwargs.get("file_permissions_mode").unwrap(), "0o755");
401 assert_eq!(kwargs.get("directory_permissions_mode").unwrap(), "0o600");
402 }
403
404 #[tokio::test]
405 async fn test_inmemory_settings_changed() {
406 let storage = InMemoryStorage::new("explicit_location", "explicit_base_url/")
408 .with_permissions(Some(0o666), Some(0o666));
409
410 assert_eq!(storage.base_location(), "explicit_location");
411 assert_eq!(storage.base_url(), "explicit_base_url/");
412 assert_eq!(storage.file_permissions_mode(), Some(0o666));
413 assert_eq!(storage.directory_permissions_mode(), Some(0o666));
414
415 let defaults_storage = InMemoryStorage::new("media_root", "media_url/");
417 assert_eq!(defaults_storage.base_location(), "media_root");
418 assert_eq!(defaults_storage.base_url(), "media_url/");
419 }
420}