about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2021-10-02 21:30:03 +0200
committerMel <einebeere@gmail.com>2021-10-02 21:30:03 +0200
commitcf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5 (patch)
treed3b415c9480ff6d57b98d6f4355bd37636b7dd2e /src
parent55da5ae8cf731c768d6cc580693068e64234a4af (diff)
downloadka-cf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5.tar.zst
ka-cf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5.zip
Immutable Fs implementation and replaced exists calls.
Diffstat (limited to 'src')
-rw-r--r--src/actions/create.rs2
-rw-r--r--src/actions/shift.rs4
-rw-r--r--src/actions/update.rs4
-rw-r--r--src/files.rs47
-rw-r--r--src/filesystem.rs178
-rw-r--r--src/history.rs8
6 files changed, 113 insertions, 130 deletions
diff --git a/src/actions/create.rs b/src/actions/create.rs
index 20dbff7..c12d416 100644
--- a/src/actions/create.rs
+++ b/src/actions/create.rs
@@ -3,7 +3,7 @@ use anyhow::Result;
 
 use super::ActionOptions;
 
-pub fn create(command_options: ActionOptions, fs: &mut impl Fs) -> Result<()> {
+pub fn create(command_options: ActionOptions, fs: &impl Fs) -> Result<()> {
     let locations = Locations::from(&command_options);
     // FIXME: Re-add old repository deletion.
     // if locations.ka_path.exists() {
diff --git a/src/actions/shift.rs b/src/actions/shift.rs
index 8f37874..b9e62ac 100644
--- a/src/actions/shift.rs
+++ b/src/actions/shift.rs
@@ -10,7 +10,7 @@ use crate::{
 
 use super::ActionOptions;
 
-pub fn shift(command_options: ActionOptions, fs: &mut impl Fs, new_cursor: usize) -> Result<()> {
+pub fn shift(command_options: ActionOptions, fs: &impl Fs, new_cursor: usize) -> Result<()> {
     let locations = Locations::from(&command_options);
 
     let repository_index_path = locations.get_repository_index_path();
@@ -38,7 +38,7 @@ pub fn shift(command_options: ActionOptions, fs: &mut impl Fs, new_cursor: usize
             acc
         })
         .iter()
-        .map(|path| FileState::from_working(&locations, path))
+        .map(|path| FileState::from_working(fs, &locations, path))
         .collect();
 
     for state in affected_files_by_shift? {
diff --git a/src/actions/update.rs b/src/actions/update.rs
index 8201862..421d5b0 100644
--- a/src/actions/update.rs
+++ b/src/actions/update.rs
@@ -11,7 +11,7 @@ use crate::{
 
 use super::ActionOptions;
 
-pub fn update(command_options: ActionOptions, fs: &mut impl Fs) -> Result<()> {
+pub fn update(command_options: ActionOptions, fs: &impl Fs) -> Result<()> {
     let timestamp = SystemTime::now()
         .duration_since(SystemTime::UNIX_EPOCH)?
         .as_secs();
@@ -51,7 +51,7 @@ pub fn update(command_options: ActionOptions, fs: &mut impl Fs) -> Result<()> {
 }
 
 fn get_new_history_for_file<FS: Fs>(
-    fs: &mut FS,
+    fs: &FS,
     cursor: usize,
     file_state: &FileState,
     locations: &Locations,
diff --git a/src/files.rs b/src/files.rs
index aceb684..0feee7f 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -18,7 +18,7 @@ impl Locations {
         return self.ka_path.join("index");
     }
 
-    pub fn get_repository_files<FS: Fs>(&self, fs: &mut FS) -> Result<Vec<FileState>, Error> {
+    pub fn get_repository_files<FS: Fs>(&self, fs: &FS) -> Result<Vec<FileState>, Error> {
         let working_entries = fs
             .read_directory(&self.repository_path)
             .context("Failed reading working file entries.")?
@@ -30,12 +30,12 @@ impl Locations {
             .context("Failed reading history file entries.")?;
 
         let working_files = Self::walk_directory(fs, working_entries, &|entry| {
-            FileState::from_working(self, &entry.path()).ok()
+            FileState::from_working(fs, self, &entry.path()).ok()
         })?;
 
         let deleted_files = Self::walk_directory(fs, history_entries, &|entry| {
             let file_path = entry.path();
-            let file = FileState::from_history(self, &file_path).ok()?;
+            let file = FileState::from_history(fs, self, &file_path).ok()?;
             match file {
                 FileState::Deleted { .. } => Some(file),
                 FileState::Tracked { .. } => None,
@@ -60,7 +60,7 @@ impl Locations {
     }
 
     fn walk_directory<FS: Fs>(
-        fs: &mut FS,
+        fs: &FS,
         directory: Vec<FS::Entry>,
         filter_map: &dyn Fn(&FS::Entry) -> Option<FileState>,
     ) -> Result<Vec<FileState>> {
@@ -100,9 +100,13 @@ pub enum FileState {
 }
 
 impl FileState {
-    pub fn from_history(locations: &Locations, history_file_path: &Path) -> Result<Self> {
+    pub fn from_history<FS: Fs>(
+        fs: &FS,
+        locations: &Locations,
+        history_file_path: &Path,
+    ) -> Result<Self> {
         let working_path = locations.working_from_history(history_file_path)?;
-        Ok(if !working_path.exists() {
+        Ok(if !fs.path_exists(&working_path) {
             FileState::Deleted(FileDeleted {
                 history_path: history_file_path.to_path_buf(),
             })
@@ -114,11 +118,14 @@ impl FileState {
         })
     }
 
-    pub fn from_working(locations: &Locations, working_file_path: &Path) -> Result<Self> {
+    pub fn from_working<FS: Fs>(
+        fs: &FS,
+        locations: &Locations,
+        working_file_path: &Path,
+    ) -> Result<Self> {
         let history_path = locations.history_from_working(working_file_path)?;
-        // FIXME: Path::exists wouldn't work with Fs abstraction.
         // TODO: Think whether abstracting Path would be needed for Fs abstraction.
-        Ok(if !history_path.exists() {
+        Ok(if !fs.path_exists(&history_path) {
             FileState::Untracked(FileUntracked {
                 path: working_file_path.to_path_buf(),
             })
@@ -144,15 +151,11 @@ pub struct FileDeleted {
 }
 
 impl FileDeleted {
-    pub fn load_history_file<FS: Fs>(&self, fs: &mut FS) -> Result<FS::File> {
+    pub fn load_history_file<FS: Fs>(&self, fs: &FS) -> Result<FS::File> {
         fs.open_writable_file(&self.history_path)
     }
 
-    pub fn create_working_file<FS: Fs>(
-        &self,
-        fs: &mut FS,
-        locations: &Locations,
-    ) -> Result<FS::File> {
+    pub fn create_working_file<FS: Fs>(&self, fs: &FS, locations: &Locations) -> Result<FS::File> {
         let working_path = locations.working_from_history(&self.history_path)?;
         fs.create_file(&working_path)
     }
@@ -163,15 +166,11 @@ pub struct FileUntracked {
 }
 
 impl FileUntracked {
-    pub fn load_file<FS: Fs>(&self, fs: &mut FS) -> Result<FS::File> {
+    pub fn load_file<FS: Fs>(&self, fs: &FS) -> Result<FS::File> {
         fs.open_readable_file(&self.path)
     }
 
-    pub fn create_history_file<FS: Fs>(
-        &self,
-        fs: &mut FS,
-        locations: &Locations,
-    ) -> Result<FS::File> {
+    pub fn create_history_file<FS: Fs>(&self, fs: &FS, locations: &Locations) -> Result<FS::File> {
         let history_path = locations.history_from_working(&self.path)?;
         Ok(fs.create_file(&history_path)?)
     }
@@ -183,15 +182,15 @@ pub struct FileTracked {
 }
 
 impl FileTracked {
-    pub fn load_history_file<FS: Fs>(&self, fs: &mut FS) -> Result<FS::File> {
+    pub fn load_history_file<FS: Fs>(&self, fs: &FS) -> Result<FS::File> {
         fs.open_writable_file(&self.history_path)
     }
 
-    pub fn load_working_file<FS: Fs>(&self, fs: &mut FS) -> Result<FS::File> {
+    pub fn load_working_file<FS: Fs>(&self, fs: &FS) -> Result<FS::File> {
         fs.open_readable_file(&self.working_path)
     }
 
-    pub fn create_working_file<FS: Fs>(&self, fs: &mut FS) -> Result<FS::File> {
+    pub fn create_working_file<FS: Fs>(&self, fs: &FS) -> Result<FS::File> {
         fs.create_file(&self.working_path)
     }
 }
diff --git a/src/filesystem.rs b/src/filesystem.rs
index d8c96a1..74d7787 100644
--- a/src/filesystem.rs
+++ b/src/filesystem.rs
@@ -9,17 +9,17 @@ 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 create_file(&self, path: &Path) -> Result<Self::File>;
+    fn delete_file(&self, path: &Path) -> Result<()>;
+    fn open_readable_file(&self, path: &Path) -> Result<Self::File>;
+    fn open_writable_file(&self, path: &Path) -> Result<Self::File>;
 
-    fn read_directory(&mut self, path: &Path) -> Result<Vec<Self::Entry>>;
+    fn read_directory(&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 write_to_file(&self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()>;
+    fn read_from_file(&self, file: &mut Self::File) -> Result<Vec<u8>>;
 
-    fn path_exists(&mut self, path: &Path) -> bool;
+    fn path_exists(&self, path: &Path) -> bool;
 }
 
 pub trait FsEntry {
@@ -33,7 +33,7 @@ impl Fs for FsImpl {
     type File = File;
     type Entry = DirEntry;
 
-    fn create_file(&mut self, path: &Path) -> Result<Self::File> {
+    fn create_file(&self, path: &Path) -> Result<Self::File> {
         if let Some(parent_path) = path.parent() {
             if !parent_path.exists() {
                 fs::create_dir_all(parent_path)?;
@@ -48,17 +48,17 @@ impl Fs for FsImpl {
             .with_context(|| format!("Failed creating '{}'.", path.display()))
     }
 
-    fn delete_file(&mut self, path: &Path) -> Result<()> {
+    fn delete_file(&self, path: &Path) -> Result<()> {
         fs::remove_file(path)?;
         Ok(())
     }
 
-    fn open_readable_file(&mut self, path: &Path) -> Result<Self::File> {
+    fn open_readable_file(&self, path: &Path) -> Result<Self::File> {
         File::open(path)
             .with_context(|| format!("Failed opening '{}' for reading.", path.display()))
     }
 
-    fn open_writable_file(&mut self, path: &Path) -> Result<Self::File> {
+    fn open_writable_file(&self, path: &Path) -> Result<Self::File> {
         OpenOptions::new()
             .read(true)
             .write(true)
@@ -71,25 +71,25 @@ impl Fs for FsImpl {
             })
     }
 
-    fn read_directory(&mut self, path: &Path) -> Result<Vec<Self::Entry>> {
+    fn read_directory(&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<()> {
+    fn write_to_file(&self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()> {
         file.rewind()?;
         file.set_len(0)?;
         file.write_all(&buffer)?;
         Ok(())
     }
 
-    fn read_from_file(&mut self, file: &mut Self::File) -> Result<Vec<u8>> {
+    fn read_from_file(&self, file: &mut Self::File) -> Result<Vec<u8>> {
         let mut buffer = Vec::new();
         file.read_to_end(&mut buffer)?;
         Ok(buffer)
     }
 
-    fn path_exists(&mut self, path: &Path) -> bool {
+    fn path_exists(&self, path: &Path) -> bool {
         path.exists()
     }
 }
@@ -109,36 +109,63 @@ impl FsEntry for DirEntry {
 #[allow(dead_code)]
 #[cfg(test)]
 pub mod mock {
-    use anyhow::{Error, Result};
-    use std::path::{Path, PathBuf};
+    use anyhow::Result;
+    use std::{
+        path::{Path, PathBuf},
+        sync::{Arc, Mutex},
+    };
 
     use super::{Fs, FsEntry};
 
     pub struct FsMock {
+        state: Arc<Mutex<FsMockState>>,
+    }
+
+    struct FsMockState {
         expected_calls: Vec<ExpectedCall>,
         received_calls: Vec<ReceivedCall>,
     }
 
     impl FsMock {
         pub fn new() -> Self {
-            FsMock {
+            let state = FsMockState {
                 expected_calls: Vec::new(),
                 received_calls: Vec::new(),
+            };
+
+            FsMock {
+                state: Arc::new(Mutex::new(state)),
             }
         }
+
+        fn add_call(&self, path: &Path, variant: ReceivedCallVariant) {
+            let call = ReceivedCall {
+                affected_path: path.to_path_buf(),
+                variant,
+            };
+
+            let mut state = self.state.lock().expect("File system mock lock poisoned.");
+            state.received_calls.push(call);
+        }
+
+        fn get_expected_call(&self) -> ExpectedCallVariant {
+            let state = self.state.lock().expect("File system lock poisoned.");
+
+            let expected_call = state
+                .expected_calls
+                .get(state.received_calls.len() - 1)
+                .expect("Unexpected call.");
+
+            expected_call.variant.clone()
+        }
     }
 
     impl Fs for FsMock {
         type File = FileMock;
         type Entry = EntryMock;
 
-        fn create_file(&mut self, path: &Path) -> Result<Self::File> {
-            let call = ReceivedCall {
-                affected_path: path.to_path_buf(),
-                variant: ReceivedCallVariant::FileCreated,
-            };
-
-            self.received_calls.push(call);
+        fn create_file(&self, path: &Path) -> Result<Self::File> {
+            self.add_call(path, ReceivedCallVariant::FileCreated);
 
             Ok(FileMock {
                 path: path.to_path_buf(),
@@ -146,24 +173,14 @@ 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);
+        fn delete_file(&self, path: &Path) -> Result<()> {
+            self.add_call(path, ReceivedCallVariant::FileDeleted);
 
             Ok(())
         }
 
-        fn open_readable_file(&mut self, path: &Path) -> Result<Self::File> {
-            let call = ReceivedCall {
-                affected_path: path.to_path_buf(),
-                variant: ReceivedCallVariant::ReadableFileOpened,
-            };
-
-            self.received_calls.push(call);
+        fn open_readable_file(&self, path: &Path) -> Result<Self::File> {
+            self.add_call(path, ReceivedCallVariant::ReadableFileOpened);
 
             Ok(FileMock {
                 path: path.to_path_buf(),
@@ -171,13 +188,8 @@ pub mod mock {
             })
         }
 
-        fn open_writable_file(&mut self, path: &Path) -> Result<Self::File> {
-            let call = ReceivedCall {
-                affected_path: path.to_path_buf(),
-                variant: ReceivedCallVariant::WritableFileOpened,
-            };
-
-            self.received_calls.push(call);
+        fn open_writable_file(&self, path: &Path) -> Result<Self::File> {
+            self.add_call(path, ReceivedCallVariant::WritableFileOpened);
 
             Ok(FileMock {
                 path: path.to_path_buf(),
@@ -185,73 +197,44 @@ 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);
+        fn read_directory(&self, path: &Path) -> Result<Vec<Self::Entry>> {
+            self.add_call(path, ReceivedCallVariant::DirectoryRead);
 
-            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());
-                }
+            if let ExpectedCallVariant::ReadDirectory { entries } = self.get_expected_call() {
+                Ok(entries.to_vec())
+            } else {
+                panic!("No content to read.");
             }
-
-            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_path: file.path.clone(),
-                variant: ReceivedCallVariant::FileWritten {
+        fn write_to_file(&self, file: &mut Self::File, buffer: Vec<u8>) -> Result<()> {
+            self.add_call(
+                &file.path,
+                ReceivedCallVariant::FileWritten {
                     written_content: buffer,
                 },
-            };
+            );
 
             // TODO: Check whether file was opened with write flag.
 
-            self.received_calls.push(call);
-
             Ok(())
         }
 
-        fn read_from_file(&mut self, file: &mut Self::File) -> Result<Vec<u8>> {
-            let call = ReceivedCall {
-                affected_path: file.path.clone(),
-                variant: ReceivedCallVariant::FileRead,
-            };
+        fn read_from_file(&self, file: &mut Self::File) -> Result<Vec<u8>> {
+            self.add_call(&file.path, ReceivedCallVariant::FileRead);
 
-            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::ReadFile { read_content } = &expected_call.variant {
-                    return Ok(read_content.clone());
-                }
+            if let ExpectedCallVariant::ReadFile { read_content } = self.get_expected_call() {
+                Ok(read_content.clone())
+            } else {
+                panic!("No content to read.");
             }
-
-            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,
-            };
+        fn path_exists(&self, path: &Path) -> bool {
+            self.add_call(path, 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;
-                }
+            if let ExpectedCallVariant::PathExists(answer) = self.get_expected_call() {
+                return answer;
             }
 
             panic!("No mocked return value given for call.");
@@ -285,6 +268,7 @@ pub mod mock {
         pub variant: ExpectedCallVariant,
     }
 
+    #[derive(Clone)]
     pub enum ExpectedCallVariant {
         CreateFile,
         DeleteFile,
diff --git a/src/history.rs b/src/history.rs
index 4f23790..d023330 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -13,7 +13,7 @@ pub struct RepositoryHistory {
 }
 
 impl RepositoryHistory {
-    pub fn from_file<FS: Fs>(fs: &mut FS, file: &mut FS::File) -> Result<RepositoryHistory> {
+    pub fn from_file<FS: Fs>(fs: &FS, file: &mut FS::File) -> Result<RepositoryHistory> {
         let buffer = fs
             .read_from_file(file)
             .context("Failed reading repository history.")?;
@@ -22,7 +22,7 @@ impl RepositoryHistory {
         repository_history.context("Corrupted repository history.")
     }
 
-    pub fn write_to_file<FS: Fs>(&self, fs: &mut FS, file: &mut FS::File) -> anyhow::Result<()> {
+    pub fn write_to_file<FS: Fs>(&self, fs: &FS, file: &mut FS::File) -> anyhow::Result<()> {
         let encoded: Vec<u8> = serde_json::to_vec(self)?;
         fs.write_to_file(file, encoded)?;
         Ok(())
@@ -58,7 +58,7 @@ pub struct FileHistory {
 }
 
 impl FileHistory {
-    pub fn from_file<FS: Fs>(fs: &mut FS, file: &mut FS::File) -> Result<FileHistory> {
+    pub fn from_file<FS: Fs>(fs: &FS, file: &mut FS::File) -> Result<FileHistory> {
         let buffer = fs
             .read_from_file(file)
             .context("Failed reading file history.")?;
@@ -67,7 +67,7 @@ impl FileHistory {
         file_history.context("Corrupted file history.")
     }
 
-    pub fn write_to_file<FS: Fs>(&self, fs: &mut FS, file: &mut FS::File) -> Result<()> {
+    pub fn write_to_file<FS: Fs>(&self, fs: &FS, file: &mut FS::File) -> Result<()> {
         let encoded: Vec<u8> = serde_json::to_vec(self)?;
         fs.write_to_file(file, encoded)?;
         Ok(())