diff options
| author | Mel <einebeere@gmail.com> | 2021-10-02 21:30:03 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2021-10-02 21:30:03 +0200 |
| commit | cf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5 (patch) | |
| tree | d3b415c9480ff6d57b98d6f4355bd37636b7dd2e /src | |
| parent | 55da5ae8cf731c768d6cc580693068e64234a4af (diff) | |
| download | ka-cf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5.tar.zst ka-cf01169e467f4ac36d2d8b9ce13e7bbc81d46aa5.zip | |
Immutable Fs implementation and replaced exists calls.
Diffstat (limited to 'src')
| -rw-r--r-- | src/actions/create.rs | 2 | ||||
| -rw-r--r-- | src/actions/shift.rs | 4 | ||||
| -rw-r--r-- | src/actions/update.rs | 4 | ||||
| -rw-r--r-- | src/files.rs | 47 | ||||
| -rw-r--r-- | src/filesystem.rs | 178 | ||||
| -rw-r--r-- | src/history.rs | 8 |
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(()) |
