1use rand::distributions::{Alphanumeric, DistString};
2use std::cell::RefCell;
3use std::fs;
4use std::io::{Read, Seek, Write};
5use std::path::{Path, PathBuf};
6
7#[cfg(test)]
8use std::collections::HashMap;
9#[cfg(test)]
10use std::io;
11#[cfg(test)]
12use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
13#[cfg(test)]
14use std::thread;
15
16#[derive(Debug)]
17pub enum FileMode {
18 Read,
19 Write,
20}
21
22#[derive(Debug)]
23pub enum Error {
24 CreateDir,
25 CreateFile,
26 OpenFile(FileMode),
27 RemoveFile,
28 RemoveDir,
29 DirEntries,
30 FlushFile,
31 FileAccess,
32 FileLen,
33}
34
35impl std::fmt::Display for Error {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Error::CreateDir => f.write_str("Unable to create a new directory"),
39 Error::CreateFile => f.write_str("Unable to create a new file"),
40 Error::OpenFile(mode) => write!(f, "Unable to read the file in {mode:?} mode"),
41 Error::FlushFile => f.write_str("Unable to flush the file"),
42 Error::RemoveFile => f.write_str("Unable to remove the file"),
43 Error::RemoveDir => f.write_str("Unable to remove dir"),
44 Error::DirEntries => f.write_str("Unable to read directory"),
45 Error::FileAccess => f.write_str("Permission denied"),
46 Error::FileLen => f.write_str("Unable to get file length"),
47 }
48 }
49}
50
51impl std::error::Error for Error {}
52
53pub trait Storage<RW>: Send + Sync
54where
55 RW: Read + Write + Seek,
56{
57 fn create_temp_file(&self) -> Result<Entry<RW>, Error> {
59 let mut path = std::env::temp_dir();
60 let file_name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
61 path.push(file_name);
62
63 self.create_file(path)
64 }
65
66 fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<(), Error>;
67 fn create_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<RW>, Error>;
68 fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<RW>, Error>;
69 fn write_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<RW>, Error>;
70 fn flush_file(&self, file: &Entry<RW>) -> Result<(), Error>;
71 fn file_len(&self, file: &Entry<RW>) -> Result<usize, Error>;
72 fn remove_file(&self, file: Entry<RW>) -> Result<(), Error>;
73 fn remove_dir_all(&self, file: Entry<RW>) -> Result<(), Error>;
74 fn read_dir(&self, file: &Entry<RW>) -> Result<Vec<Entry<RW>>, Error>;
76}
77
78pub struct FileStorage;
79
80impl Storage<fs::File> for FileStorage {
81 fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
82 fs::create_dir_all(&path).map_err(|_| Error::CreateDir)
83 }
84
85 fn create_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<fs::File>, Error> {
86 let path = path.as_ref().to_path_buf();
87 let file = fs::File::options()
88 .create_new(true)
89 .read(true)
90 .write(true)
91 .open(&path)
92 .map_err(|_| Error::CreateFile)?;
93 Ok(Entry::File(FileData {
94 path,
95 stream: RefCell::new(file),
96 }))
97 }
98
99 fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<fs::File>, Error> {
100 let path = path.as_ref().to_path_buf();
101 if path.is_dir() {
102 Ok(Entry::Dir(path))
103 } else {
104 let file = fs::File::open(&path).map_err(|_| Error::OpenFile(FileMode::Read))?;
105 Ok(Entry::File(FileData {
106 path,
107 stream: RefCell::new(file),
108 }))
109 }
110 }
111
112 fn write_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<fs::File>, Error> {
113 let path = path.as_ref().to_path_buf();
114 let file = fs::File::options()
115 .write(true)
116 .read(true)
117 .truncate(true)
118 .open(&path)
119 .map_err(|_| Error::OpenFile(FileMode::Write))?;
120
121 Ok(Entry::File(FileData {
122 path,
123 stream: RefCell::new(file),
124 }))
125 }
126
127 fn flush_file(&self, file: &Entry<fs::File>) -> Result<(), Error> {
128 file.try_writer()?
129 .borrow_mut()
130 .flush()
131 .map_err(|_| Error::FlushFile)
132 }
133
134 fn file_len(&self, file: &Entry<fs::File>) -> Result<usize, Error> {
135 let fs_file = match file {
136 Entry::File(FileData { stream, .. }) => stream.borrow(),
137 Entry::Dir(_) => return Err(Error::FileAccess),
138 };
139 let file_meta = fs::File::metadata(&fs_file).map_err(|_| Error::FileLen)?;
140 file_meta.len().try_into().map_err(|_| Error::FileLen)
141 }
142
143 fn remove_file(&self, file: Entry<fs::File>) -> Result<(), Error> {
144 if let Entry::File(FileData { stream, .. }) = &file {
145 let mut stream = stream.borrow_mut();
146 stream.set_len(0).map_err(|_| Error::RemoveFile)?;
147 stream.flush().map_err(|_| Error::FlushFile)?;
148 }
149
150 fs::remove_file(file.path()).map_err(|_| Error::RemoveFile)
151 }
152
153 fn remove_dir_all(&self, file: Entry<fs::File>) -> Result<(), Error> {
154 if !file.is_dir() {
155 return Err(Error::RemoveDir);
156 }
157
158 fs::remove_dir_all(file.path()).map_err(|_| Error::RemoveDir)
159 }
160
161 fn read_dir(&self, file: &Entry<fs::File>) -> Result<Vec<Entry<fs::File>>, Error> {
162 if !file.is_dir() {
163 return Err(Error::FileAccess);
164 }
165
166 walkdir::WalkDir::new(file.path())
167 .into_iter()
168 .map(|res| {
169 res.map(|e| e.path().to_owned())
170 .map_err(|_| Error::DirEntries)
171 })
172 .map(|path| path.and_then(|path| self.read_file(path)))
173 .collect()
174 }
175}
176
177#[cfg(test)]
178#[derive(Default)]
179pub struct InMemoryStorage {
180 pub files: RwLock<HashMap<PathBuf, IMFile>>,
181}
182
183#[cfg(test)]
184impl InMemoryStorage {
185 fn save_text_file<P: AsRef<Path>>(&self, path: P, content: &str) {
186 let buf = content.bytes().collect::<Vec<_>>();
187 self.save_file(
188 path,
189 IMFile::File(InMemoryFile {
190 len: buf.len(),
191 buf,
192 }),
193 );
194 }
195
196 fn save_file<P: AsRef<Path>>(&self, path: P, im_file: IMFile) {
197 self.mut_files().insert(path.as_ref().to_owned(), im_file);
198 }
199
200 pub(crate) fn files(&self) -> RwLockReadGuard<'_, HashMap<PathBuf, IMFile>> {
201 loop {
202 match self.files.try_read() {
203 Ok(files) => break files,
204 _ => thread::sleep(std::time::Duration::from_micros(100)),
205 }
206 }
207 }
208
209 pub(crate) fn mut_files(&self) -> RwLockWriteGuard<'_, HashMap<PathBuf, IMFile>> {
210 loop {
211 match self.files.try_write() {
212 Ok(files) => break files,
213 _ => thread::sleep(std::time::Duration::from_micros(100)),
214 }
215 }
216 }
217
218 pub(crate) fn add_hello_txt(&self) {
223 self.save_text_file("hello.txt", "hello world");
224 }
225
226 pub(crate) fn add_bar_foo_folder(&self) {
227 self.save_file("bar/", IMFile::Dir);
228 self.save_text_file("bar/hello.txt", "hello");
229 self.save_text_file("bar/world.txt", "world");
230 self.save_file("bar/foo/", IMFile::Dir);
231 self.save_text_file("bar/foo/hello.txt", "hello");
232 self.save_text_file("bar/foo/world.txt", "world");
233 }
234
235 pub(crate) fn add_bar_foo_folder_with_hidden(&self) {
236 self.save_file("bar/", IMFile::Dir);
237 self.save_text_file("bar/.hello.txt", "hello");
238 self.save_text_file("bar/world.txt", "world");
239 self.save_file("bar/.foo/", IMFile::Dir);
240 self.save_text_file("bar/.foo/hello.txt", "hello");
241 self.save_text_file("bar/.foo/world.txt", "world");
242 }
243}
244
245#[cfg(test)]
246impl Storage<io::Cursor<Vec<u8>>> for InMemoryStorage {
247 fn create_dir_all<P: AsRef<Path>>(&self, _path: P) -> Result<(), Error> {
248 todo!();
249 }
250
251 fn create_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<io::Cursor<Vec<u8>>>, Error> {
252 let file_path = path.as_ref().to_path_buf();
253
254 #[allow(clippy::significant_drop_in_scrutinee)]
255 let im_file = match self.files().get(&file_path) {
256 Some(_) => Err(Error::CreateFile),
257 None => Ok(IMFile::File(InMemoryFile::default())),
258 }?;
259
260 let cursor = io::Cursor::new(im_file.inner().buf.clone());
261
262 self.save_file(file_path.clone(), im_file);
263
264 Ok(Entry::File(FileData {
265 path: file_path,
266 stream: RefCell::new(cursor),
267 }))
268 }
269
270 fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<io::Cursor<Vec<u8>>>, Error> {
271 let in_file = self
272 .files()
273 .get(path.as_ref())
274 .cloned()
275 .ok_or(Error::OpenFile(FileMode::Read))?;
276
277 let file_path = path.as_ref().to_path_buf();
278
279 match in_file {
280 IMFile::Dir => Ok(Entry::Dir(file_path)),
281 IMFile::File(f) => {
282 let cursor = io::Cursor::new(f.buf);
283 Ok(Entry::File(FileData {
284 path: file_path,
285 stream: RefCell::new(cursor),
286 }))
287 }
288 }
289 }
290
291 fn write_file<P: AsRef<Path>>(&self, path: P) -> Result<Entry<io::Cursor<Vec<u8>>>, Error> {
292 let file_path = path.as_ref().to_path_buf();
293
294 let file = self
295 .files()
296 .get(&file_path)
297 .cloned()
298 .ok_or(Error::OpenFile(FileMode::Write))?;
299 if matches!(file, IMFile::Dir) {
300 return Err(Error::FileAccess);
301 }
302
303 let cursor = io::Cursor::new(file.inner().buf.clone());
304
305 Ok(Entry::File(FileData {
306 path: file_path,
307 stream: RefCell::new(cursor),
308 }))
309 }
310
311 fn flush_file(&self, file: &Entry<io::Cursor<Vec<u8>>>) -> Result<(), Error> {
312 if file.is_dir() {
313 return Err(Error::FileAccess);
314 }
315
316 let file_path = file.path();
317 let writer = file.try_writer()?;
318 writer.borrow_mut().flush().map_err(|_| Error::FlushFile)?;
319
320 let vec = writer.borrow().get_ref().clone();
321 let len = vec.len();
322 let new_file = IMFile::File(InMemoryFile { buf: vec, len });
323
324 self.save_file(file_path, new_file);
325
326 Ok(())
327 }
328
329 fn file_len(&self, file: &Entry<io::Cursor<Vec<u8>>>) -> Result<usize, Error> {
330 let cur = match file {
331 Entry::File(FileData { stream, .. }) => stream.borrow(),
332 Entry::Dir(_) => return Err(Error::FileAccess),
333 };
334
335 Ok(cur.get_ref().len())
336 }
337
338 fn remove_file(&self, file: Entry<io::Cursor<Vec<u8>>>) -> Result<(), Error> {
339 self.mut_files()
340 .remove(file.path())
341 .ok_or(Error::RemoveFile)?;
342 Ok(())
343 }
344
345 fn remove_dir_all(&self, file: Entry<io::Cursor<Vec<u8>>>) -> Result<(), Error> {
346 if !file.is_dir() {
347 return Err(Error::FileAccess);
348 }
349
350 let file_path = file.path();
351
352 #[allow(clippy::needless_collect)] let file_paths = self
354 .files()
355 .keys()
356 .filter(|k| k.starts_with(file_path))
357 .cloned()
358 .collect::<Vec<_>>();
359
360 file_paths.into_iter().try_for_each(|k| {
361 self.mut_files()
362 .remove(&k)
363 .map(|_| ())
364 .ok_or(Error::RemoveDir)?;
365 Ok(())
366 })
367 }
368
369 fn read_dir(
370 &self,
371 file: &Entry<io::Cursor<Vec<u8>>>,
372 ) -> Result<Vec<Entry<io::Cursor<Vec<u8>>>>, Error> {
373 if !file.is_dir() {
374 return Err(Error::FileAccess);
375 }
376
377 let file_path = file.path();
378
379 self.files()
380 .iter()
381 .filter(|(k, _)| k.starts_with(file_path))
382 .map(|(k, _)| self.read_file(k))
383 .collect()
384 }
385}
386
387#[cfg(test)]
388#[derive(Debug, Clone, Default, PartialEq, Eq)]
389pub struct InMemoryFile {
390 pub buf: Vec<u8>,
391 pub len: usize,
392}
393
394#[cfg(test)]
395#[derive(Debug, Clone, PartialEq, Eq)]
396pub enum IMFile {
397 File(InMemoryFile),
398 Dir,
399}
400
401#[cfg(test)]
402impl IMFile {
403 fn inner(&self) -> &InMemoryFile {
404 match self {
405 IMFile::File(inner) => inner,
406 IMFile::Dir => unreachable!(),
407 }
408 }
409}
410
411pub struct FileData<RW>
412where
413 RW: Read + Write + Seek,
414{
415 path: PathBuf,
416 stream: RefCell<RW>,
417}
418
419pub enum Entry<RW>
420where
421 RW: Read + Write + Seek,
422{
423 File(FileData<RW>),
424 Dir(PathBuf),
425}
426
427impl<RW> Entry<RW>
428where
429 RW: Read + Write + Seek,
430{
431 pub fn path(&self) -> &Path {
432 match self {
433 Entry::File(FileData { path, .. }) | Entry::Dir(path) => path,
434 }
435 }
436
437 pub fn is_dir(&self) -> bool {
438 matches!(self, Entry::Dir(_))
439 }
440
441 pub fn try_reader(&self) -> Result<&RefCell<RW>, Error> {
442 match self {
443 Entry::File(file) => Ok(&file.stream),
444 Entry::Dir(_) => Err(Error::FileAccess),
445 }
446 }
447
448 pub fn try_writer(&self) -> Result<&RefCell<RW>, Error> {
449 match self {
450 Entry::File(file) => Ok(&file.stream),
451 Entry::Dir(_) => Err(Error::FileAccess),
452 }
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 fn sorted_file_names(file_names: &[PathBuf]) -> Vec<&str> {
461 let mut keys = file_names
462 .iter()
463 .map(|k| k.to_str().unwrap())
464 .collect::<Vec<_>>();
465 keys.sort_unstable();
466 keys
467 }
468
469 #[test]
470 fn should_create_a_new_file() {
471 let stor = InMemoryStorage::default();
472
473 match stor.create_file("hello.txt") {
474 Ok(file) => {
475 let im_file = stor.files().get(file.path()).cloned();
476 assert_eq!(im_file, Some(IMFile::File(InMemoryFile::default())));
477 }
478 _ => unreachable!(),
479 }
480 }
481
482 #[test]
483 fn should_throw_an_error_if_file_already_exist() {
484 let stor = InMemoryStorage::default();
485 stor.add_hello_txt();
486
487 match stor.create_file("hello.txt") {
488 Err(Error::CreateFile) => {}
489 _ => unreachable!(),
490 }
491 }
492
493 #[test]
494 fn should_not_open_file_to_read() {
495 let stor = InMemoryStorage::default();
496
497 match stor.read_file("hello.txt") {
498 Err(Error::OpenFile(FileMode::Read)) => {}
499 _ => unreachable!(),
500 }
501 }
502
503 #[test]
504 fn should_not_open_file_to_write() {
505 let stor = InMemoryStorage::default();
506
507 match stor.write_file("hello.txt") {
508 Err(Error::OpenFile(FileMode::Write)) => {}
509 _ => unreachable!(),
510 }
511 }
512
513 #[test]
514 fn should_open_exist_file_in_read_mode() {
515 let stor = InMemoryStorage::default();
516 stor.add_hello_txt();
517
518 match stor.read_file("hello.txt") {
519 Ok(file) => {
520 if let Some(IMFile::File(InMemoryFile { buf, len })) = stor.files().get(file.path())
521 {
522 let content = b"hello world".to_vec();
523 assert_eq!(len, &content.len());
524 assert_eq!(buf, &content);
525 } else {
526 unreachable!();
527 }
528 }
529 _ => unreachable!(),
530 }
531 }
532
533 #[test]
534 fn should_open_exist_file_in_write_mode() {
535 let stor = InMemoryStorage::default();
536 stor.add_hello_txt();
537
538 match stor.write_file("hello.txt") {
539 Ok(file) => {
540 if let Some(IMFile::File(InMemoryFile { buf, len })) = stor.files().get(file.path())
541 {
542 let content = b"hello world".to_vec();
543 assert_eq!(len, &content.len());
544 assert_eq!(buf, &content);
545 } else {
546 unreachable!();
547 }
548 }
549 _ => unreachable!(),
550 }
551 }
552
553 #[test]
554 fn should_write_content_to_file() {
555 let stor = InMemoryStorage::default();
556 let content = "hello world";
557
558 let file = stor.create_file("hello.txt").unwrap();
559 file.try_writer()
560 .unwrap()
561 .borrow_mut()
562 .write_all(content.as_bytes())
563 .unwrap();
564
565 match stor.flush_file(&file) {
566 Ok(_) => {
567 let im_file = stor.files().get(file.path()).cloned();
568 assert_eq!(
569 im_file,
570 Some(IMFile::File(InMemoryFile {
571 buf: content.as_bytes().to_vec(),
572 len: content.len()
573 }))
574 );
575 }
576 _ => unreachable!(),
577 }
578 }
579
580 #[test]
581 fn should_remove_a_file_in_read_mode() {
582 let stor = InMemoryStorage::default();
583 stor.add_hello_txt();
584
585 let file = stor.write_file("hello.txt").unwrap();
586 let file_path = file.path().to_path_buf();
587
588 match stor.remove_file(file) {
589 Ok(_) => {
590 let im_file = stor.files().get(&file_path).cloned();
591 assert_eq!(im_file, None);
592 }
593 _ => unreachable!(),
594 }
595 }
596
597 #[test]
598 fn should_remove_a_file_in_write_mode() {
599 let stor = InMemoryStorage::default();
600 stor.add_hello_txt();
601
602 let file = stor.write_file("hello.txt").unwrap();
603 let file_path = file.path().to_path_buf();
604
605 match stor.remove_file(file) {
606 Ok(_) => {
607 let im_file = stor.files().get(&file_path).cloned();
608 assert_eq!(im_file, None);
609 }
610 _ => unreachable!(),
611 }
612 }
613
614 #[test]
615 fn should_get_file_length() {
616 let stor = InMemoryStorage::default();
617 stor.add_hello_txt();
618
619 let file = stor.read_file("hello.txt").unwrap();
620
621 match stor.file_len(&file) {
622 Ok(len) => {
623 let content = b"hello world".to_vec();
624 assert_eq!(len, content.len());
625 }
626 _ => unreachable!(),
627 }
628 }
629
630 #[test]
631 fn should_open_dir() {
632 let stor = InMemoryStorage::default();
633 stor.add_bar_foo_folder();
634
635 match stor.read_file("bar/foo/") {
636 Ok(Entry::Dir(path)) => assert_eq!(path, PathBuf::from("bar/foo/")),
637 _ => unreachable!(),
638 }
639 }
640
641 #[test]
642 fn should_remove_dir_with_subfiles() {
643 let stor = InMemoryStorage::default();
644 stor.add_hello_txt();
645 stor.add_bar_foo_folder();
646
647 let file = stor.read_file("bar/foo/").unwrap();
648 let file_path = file.path().to_path_buf();
649
650 match stor.remove_dir_all(file) {
651 Ok(()) => {
652 assert_eq!(stor.files().get(&file_path).cloned(), None);
653 let files = stor.files();
654 let keys = files.keys().cloned().collect::<Vec<_>>();
655 assert_eq!(
656 sorted_file_names(&keys),
657 vec!["bar/", "bar/hello.txt", "bar/world.txt", "hello.txt"]
658 );
659 }
660 _ => unreachable!(),
661 }
662 }
663
664 #[test]
665 fn should_remove_dir_recursively_with_subfiles() {
666 let stor = InMemoryStorage::default();
667 stor.add_hello_txt();
668 stor.add_bar_foo_folder();
669
670 let file = stor.read_file("bar/").unwrap();
671 let file_path = file.path().to_path_buf();
672
673 match stor.remove_dir_all(file) {
674 Ok(()) => {
675 assert_eq!(stor.files().get(&file_path).cloned(), None);
676 let files = stor.files();
677 let keys = files.keys().cloned().collect::<Vec<PathBuf>>();
678 assert_eq!(sorted_file_names(&keys), vec!["hello.txt"]);
679 }
680 _ => unreachable!(),
681 }
682 }
683
684 #[test]
685 fn should_return_file_names_of_dir_subfiles() {
686 let stor = InMemoryStorage::default();
687 stor.add_hello_txt();
688 stor.add_bar_foo_folder();
689
690 let file = stor.read_file("bar/").unwrap();
691
692 match stor.read_dir(&file) {
693 Ok(files) => {
694 let file_names = files
695 .iter()
696 .map(|f| f.path().to_path_buf())
697 .collect::<Vec<_>>();
698 assert_eq!(
699 sorted_file_names(&file_names),
700 vec![
701 "bar/",
702 "bar/foo/",
703 "bar/foo/hello.txt",
704 "bar/foo/world.txt",
705 "bar/hello.txt",
706 "bar/world.txt",
707 ]
708 );
709 }
710 _ => unreachable!(),
711 }
712 }
713
714 #[test]
715 fn should_include_hidden_files_names() {
716 let stor = InMemoryStorage::default();
717 stor.add_hello_txt();
718 stor.add_bar_foo_folder_with_hidden();
719
720 let file = stor.read_file("bar/").unwrap();
721
722 match stor.read_dir(&file) {
723 Ok(files) => {
724 let file_names = files
725 .into_iter()
726 .map(|f| f.path().to_path_buf())
727 .collect::<Vec<_>>();
728 assert_eq!(
729 sorted_file_names(&file_names),
730 vec![
731 "bar/",
732 "bar/.foo/",
733 "bar/.foo/hello.txt",
734 "bar/.foo/world.txt",
735 "bar/.hello.txt",
736 "bar/world.txt",
737 ]
738 );
739 }
740 _ => unreachable!(),
741 }
742 }
743}