about summary refs log tree commit diff
path: root/src/filesystem.rs
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2021-10-02 19:55:01 +0200
committerMel <einebeere@gmail.com>2021-10-02 19:55:01 +0200
commit55da5ae8cf731c768d6cc580693068e64234a4af (patch)
tree9024bf8c315f1c150a468db6791acbeec2907601 /src/filesystem.rs
parentc1eccadf76ca7625d8cff9a52480168aab876e62 (diff)
downloadka-55da5ae8cf731c768d6cc580693068e64234a4af.tar.zst
ka-55da5ae8cf731c768d6cc580693068e64234a4af.zip
Use filesystem abstraction throughout Ka.
Diffstat (limited to 'src/filesystem.rs')
-rw-r--r--src/filesystem.rs141
1 files changed, 129 insertions, 12 deletions
diff --git a/src/filesystem.rs b/src/filesystem.rs
index ab5bf66..d8c96a1 100644
--- a/src/filesystem.rs
+++ b/src/filesystem.rs
@@ -1,27 +1,45 @@
 use anyhow::{Context, Result};
 use std::{
-    fs::{File, OpenOptions},
-    io::{Read, Seek, Write},
-    path::Path,
+    fs::{self, DirEntry, File, OpenOptions},
+    io::{self, Read, Seek, Write},
+    path::{Path, PathBuf},
 };
 
 pub trait Fs {
     type File;
+    type Entry: FsEntry;
 
     fn create_file(&mut self, path: &Path) -> Result<Self::File>;
+    fn delete_file(&mut self, path: &Path) -> Result<()>;
     fn open_readable_file(&mut self, path: &Path) -> Result<Self::File>;
     fn open_writable_file(&mut self, path: &Path) -> Result<Self::File>;
 
+    fn read_directory(&mut self, path: &Path) -> Result<Vec<Self::Entry>>;
+
     fn write_to_file(&mut self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()>;
     fn read_from_file(&mut self, file: &mut Self::File) -> Result<Vec<u8>>;
+
+    fn path_exists(&mut self, path: &Path) -> bool;
+}
+
+pub trait FsEntry {
+    fn path(&self) -> PathBuf;
+    fn is_directory(&self) -> Result<bool>;
 }
 
 pub struct FsImpl {}
 
 impl Fs for FsImpl {
     type File = File;
+    type Entry = DirEntry;
 
     fn create_file(&mut self, path: &Path) -> Result<Self::File> {
+        if let Some(parent_path) = path.parent() {
+            if !parent_path.exists() {
+                fs::create_dir_all(parent_path)?;
+            }
+        }
+
         OpenOptions::new()
             .create(true)
             .read(true)
@@ -30,6 +48,11 @@ impl Fs for FsImpl {
             .with_context(|| format!("Failed creating '{}'.", path.display()))
     }
 
+    fn delete_file(&mut self, path: &Path) -> Result<()> {
+        fs::remove_file(path)?;
+        Ok(())
+    }
+
     fn open_readable_file(&mut self, path: &Path) -> Result<Self::File> {
         File::open(path)
             .with_context(|| format!("Failed opening '{}' for reading.", path.display()))
@@ -48,6 +71,11 @@ impl Fs for FsImpl {
             })
     }
 
+    fn read_directory(&mut self, path: &Path) -> Result<Vec<Self::Entry>> {
+        let result: io::Result<_> = fs::read_dir(path)?.collect();
+        result.with_context(|| format!("Failed reading directory {}", path.display()))
+    }
+
     fn write_to_file(&mut self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()> {
         file.rewind()?;
         file.set_len(0)?;
@@ -60,14 +88,31 @@ impl Fs for FsImpl {
         file.read_to_end(&mut buffer)?;
         Ok(buffer)
     }
+
+    fn path_exists(&mut self, path: &Path) -> bool {
+        path.exists()
+    }
 }
 
+impl FsEntry for DirEntry {
+    fn path(&self) -> PathBuf {
+        self.path()
+    }
+
+    fn is_directory(&self) -> Result<bool> {
+        let file_type = self.file_type()?;
+        Ok(file_type.is_dir())
+    }
+}
+
+// TODO: This will be used for tests. Write them.
+#[allow(dead_code)]
 #[cfg(test)]
 pub mod mock {
-    use std::path::{Path, PathBuf};
     use anyhow::{Error, Result};
+    use std::path::{Path, PathBuf};
 
-    use super::Fs;
+    use super::{Fs, FsEntry};
 
     pub struct FsMock {
         expected_calls: Vec<ExpectedCall>,
@@ -85,10 +130,11 @@ pub mod mock {
 
     impl Fs for FsMock {
         type File = FileMock;
+        type Entry = EntryMock;
 
         fn create_file(&mut self, path: &Path) -> Result<Self::File> {
             let call = ReceivedCall {
-                affected_file: path.to_path_buf(),
+                affected_path: path.to_path_buf(),
                 variant: ReceivedCallVariant::FileCreated,
             };
 
@@ -100,9 +146,20 @@ pub mod mock {
             })
         }
 
+        fn delete_file(&mut self, path: &Path) -> Result<()> {
+            let call = ReceivedCall {
+                affected_path: path.to_path_buf(),
+                variant: ReceivedCallVariant::FileCreated,
+            };
+
+            self.received_calls.push(call);
+
+            Ok(())
+        }
+
         fn open_readable_file(&mut self, path: &Path) -> Result<Self::File> {
             let call = ReceivedCall {
-                affected_file: path.to_path_buf(),
+                affected_path: path.to_path_buf(),
                 variant: ReceivedCallVariant::ReadableFileOpened,
             };
 
@@ -116,7 +173,7 @@ pub mod mock {
 
         fn open_writable_file(&mut self, path: &Path) -> Result<Self::File> {
             let call = ReceivedCall {
-                affected_file: path.to_path_buf(),
+                affected_path: path.to_path_buf(),
                 variant: ReceivedCallVariant::WritableFileOpened,
             };
 
@@ -128,9 +185,28 @@ pub mod mock {
             })
         }
 
+        fn read_directory(&mut self, path: &Path) -> Result<Vec<Self::Entry>> {
+            let call = ReceivedCall {
+                affected_path: path.to_path_buf(),
+                variant: ReceivedCallVariant::DirectoryRead,
+            };
+
+            self.received_calls.push(call);
+
+            let expected_call_option = self.expected_calls.get(self.received_calls.len() - 1);
+
+            if let Some(expected_call) = expected_call_option {
+                if let ExpectedCallVariant::ReadDirectory { entries } = &expected_call.variant {
+                    return Ok(entries.to_vec());
+                }
+            }
+
+            Err(Error::msg("No content to read."))
+        }
+
         fn write_to_file(&mut self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()> {
             let call = ReceivedCall {
-                affected_file: file.path.clone(),
+                affected_path: file.path.clone(),
                 variant: ReceivedCallVariant::FileWritten {
                     written_content: buffer,
                 },
@@ -145,7 +221,7 @@ pub mod mock {
 
         fn read_from_file(&mut self, file: &mut Self::File) -> Result<Vec<u8>> {
             let call = ReceivedCall {
-                affected_file: file.path.clone(),
+                affected_path: file.path.clone(),
                 variant: ReceivedCallVariant::FileRead,
             };
 
@@ -161,6 +237,41 @@ pub mod mock {
 
             Err(Error::msg("No content to read."))
         }
+
+        fn path_exists(&mut self, path: &Path) -> bool {
+            let call = ReceivedCall {
+                affected_path: path.to_path_buf(),
+                variant: ReceivedCallVariant::DoesPathExist,
+            };
+
+            self.received_calls.push(call);
+
+            let expected_call_option = self.expected_calls.get(self.received_calls.len() - 1);
+
+            if let Some(expected_call) = expected_call_option {
+                if let ExpectedCallVariant::PathExists(answer) = &expected_call.variant {
+                    return *answer;
+                }
+            }
+
+            panic!("No mocked return value given for call.");
+        }
+    }
+
+    #[derive(Clone)]
+    pub struct EntryMock {
+        path: PathBuf,
+        is_directory: bool,
+    }
+
+    impl FsEntry for EntryMock {
+        fn path(&self) -> PathBuf {
+            self.path.clone()
+        }
+
+        fn is_directory(&self) -> Result<bool> {
+            Ok(self.is_directory)
+        }
     }
 
     pub struct FileMock {
@@ -170,28 +281,34 @@ pub mod mock {
 
     // TODO: Do we need ways to return errors?
     pub struct ExpectedCall {
-        pub affected_file: PathBuf,
+        pub affected_path: PathBuf,
         pub variant: ExpectedCallVariant,
     }
 
     pub enum ExpectedCallVariant {
         CreateFile,
+        DeleteFile,
         OpenReadableFile,
         OpenWritableFile,
+        ReadDirectory { entries: Vec<EntryMock> },
         ReadFile { read_content: Vec<u8> },
         WriteToFile,
+        PathExists(bool),
     }
 
     pub struct ReceivedCall {
-        pub affected_file: PathBuf,
+        pub affected_path: PathBuf,
         pub variant: ReceivedCallVariant,
     }
 
     pub enum ReceivedCallVariant {
         FileCreated,
+        FileDeleted,
         ReadableFileOpened,
         WritableFileOpened,
+        DirectoryRead,
         FileRead,
         FileWritten { written_content: Vec<u8> },
+        DoesPathExist,
     }
 }