aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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(())