From 4abd9dd7cfae1999a681047531c73d419d837390 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 17 Jun 2017 18:52:48 +0200 Subject: [PATCH] Split into module-files --- libimagstore/src/file_abstraction.rs | 519 ------------------ libimagstore/src/file_abstraction/fs.rs | 142 +++++ libimagstore/src/file_abstraction/inmemory.rs | 166 ++++++ libimagstore/src/file_abstraction/mod.rs | 126 +++++ .../src/file_abstraction/stdio/mapper/mod.rs | 30 + .../src/file_abstraction/stdio/mod.rs | 127 +++++ 6 files changed, 591 insertions(+), 519 deletions(-) delete mode 100644 libimagstore/src/file_abstraction.rs create mode 100644 libimagstore/src/file_abstraction/fs.rs create mode 100644 libimagstore/src/file_abstraction/inmemory.rs create mode 100644 libimagstore/src/file_abstraction/mod.rs create mode 100644 libimagstore/src/file_abstraction/stdio/mapper/mod.rs create mode 100644 libimagstore/src/file_abstraction/stdio/mod.rs diff --git a/libimagstore/src/file_abstraction.rs b/libimagstore/src/file_abstraction.rs deleted file mode 100644 index ad4697d8..00000000 --- a/libimagstore/src/file_abstraction.rs +++ /dev/null @@ -1,519 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015, 2016 Matthias Beyer and contributors -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; version -// 2.1 of the License. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// - -//! The filesystem abstraction code -//! -//! # Problem -//! -//! First, we had a compiletime backend for the store. This means that the actual filesystem -//! operations were compiled into the store either as real filesystem operations (in a normal debug -//! or release build) but as a in-memory variant in the 'test' case. -//! So tests did not hit the filesystem when running. -//! This gave us us the possibility to run tests concurrently with multiple -//! stores that did not interfere with eachother. -//! -//! This approach worked perfectly well until we started to test not the -//! store itself but crates that depend on the store implementation. -//! When running tests in a crate that depends on the store, the store -//! itself was compiled with the filesystem-hitting-backend. -//! This was problematic, as tests could not be implemented without hitting -//! the filesystem. -//! -//! Hence we implemented this. -//! -//! # Implementation -//! -//! The filesystem is abstracted via a trait `FileAbstraction` which -//! contains the essential functions for working with the filesystem. -//! -//! Two implementations are provided in the code: -//! -//! * FSFileAbstraction -//! * InMemoryFileAbstraction -//! -//! whereas the first actually works with the filesystem and the latter -//! works with an in-memory HashMap that is used as filesystem. -//! -//! Further, the trait `FileAbstractionInstance` was introduced for -//! functions which are executed on actual instances of content from the -//! filesystem, which was previousely tied into the general abstraction -//! mechanism. -//! -//! So, the `FileAbstraction` trait is for working with the filesystem, the -//! `FileAbstractionInstance` trait is for working with instances of content -//! from the filesystem (speak: actual Files). -//! -//! In case of the `FSFileAbstractionInstance`, which is the implementation -//! of the `FileAbstractionInstance` for the actual filesystem-hitting code, -//! the underlying resource is managed like with the old code before. -//! The `InMemoryFileAbstractionInstance` implementation is corrosponding to -//! the `InMemoryFileAbstraction` implementation - for the in-memory -//! "filesystem". -//! -//! The implementation of the `get_file_content()` function had to be -//! changed to return a `String` rather than a `&mut Read` because of -//! lifetime issues. -//! This change is store-internally and the API of the store itself was not -//! affected. -//! - -use std::path::PathBuf; -use std::fmt::Debug; - -use error::StoreError as SE; - -pub use self::fs::FSFileAbstraction; -pub use self::fs::FSFileAbstractionInstance; -pub use self::inmemory::InMemoryFileAbstraction; -pub use self::inmemory::InMemoryFileAbstractionInstance; - -/// An abstraction trait over filesystem actions -pub trait FileAbstraction : Debug { - fn remove_file(&self, path: &PathBuf) -> Result<(), SE>; - fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>; - fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>; - fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE>; - - fn new_instance(&self, p: PathBuf) -> Box; -} - -/// An abstraction trait over actions on files -pub trait FileAbstractionInstance : Debug { - fn get_file_content(&mut self) -> Result; - fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE>; -} - -mod fs { - use std::fs::{File, OpenOptions, create_dir_all, remove_file, copy, rename}; - use std::io::{Seek, SeekFrom, Read}; - use std::path::{Path, PathBuf}; - - use error::{MapErrInto, StoreError as SE, StoreErrorKind as SEK}; - - use super::FileAbstraction; - use super::FileAbstractionInstance; - - #[derive(Debug)] - pub enum FSFileAbstractionInstance { - Absent(PathBuf), - File(File, PathBuf) - } - - impl FileAbstractionInstance for FSFileAbstractionInstance { - - /** - * Get the content behind this file - */ - fn get_file_content(&mut self) -> Result { - debug!("Getting lazy file: {:?}", self); - let (file, path) = match *self { - FSFileAbstractionInstance::File(ref mut f, _) => return { - // We seek to the beginning of the file since we expect each - // access to the file to be in a different context - try!(f.seek(SeekFrom::Start(0)) - .map_err_into(SEK::FileNotSeeked)); - - let mut s = String::new(); - f.read_to_string(&mut s) - .map_err_into(SEK::IoError) - .map(|_| s) - }, - FSFileAbstractionInstance::Absent(ref p) => - (try!(open_file(p).map_err_into(SEK::FileNotFound)), p.clone()), - }; - *self = FSFileAbstractionInstance::File(file, path); - if let FSFileAbstractionInstance::File(ref mut f, _) = *self { - let mut s = String::new(); - f.read_to_string(&mut s) - .map_err_into(SEK::IoError) - .map(|_| s) - } else { - unreachable!() - } - } - - /** - * Write the content of this file - */ - fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE> { - use std::io::Write; - let (file, path) = match *self { - FSFileAbstractionInstance::File(ref mut f, _) => return { - // We seek to the beginning of the file since we expect each - // access to the file to be in a different context - try!(f.seek(SeekFrom::Start(0)) - .map_err_into(SEK::FileNotCreated)); - f.write_all(buf).map_err_into(SEK::FileNotWritten) - }, - FSFileAbstractionInstance::Absent(ref p) => - (try!(create_file(p).map_err_into(SEK::FileNotCreated)), p.clone()), - }; - *self = FSFileAbstractionInstance::File(file, path); - if let FSFileAbstractionInstance::File(ref mut f, _) = *self { - return f.write_all(buf).map_err_into(SEK::FileNotWritten); - } - unreachable!(); - } - - } - - /// `FSFileAbstraction` state type - /// - /// A lazy file is either absent, but a path to it is available, or it is present. - #[derive(Debug)] - pub struct FSFileAbstraction { - } - - impl FSFileAbstraction { - pub fn new() -> FSFileAbstraction { - FSFileAbstraction { } - } - } - - impl FileAbstraction for FSFileAbstraction { - - fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { - remove_file(path).map_err_into(SEK::FileNotRemoved) - } - - fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - copy(from, to).map_err_into(SEK::FileNotCopied).map(|_| ()) - } - - fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - rename(from, to).map_err_into(SEK::FileNotRenamed) - } - - fn create_dir_all(&self, path: &PathBuf) -> Result<(), SE> { - create_dir_all(path).map_err_into(SEK::DirNotCreated) - } - - fn new_instance(&self, p: PathBuf) -> Box { - Box::new(FSFileAbstractionInstance::Absent(p)) - } - } - - fn open_file>(p: A) -> ::std::io::Result { - OpenOptions::new().write(true).read(true).open(p) - } - - fn create_file>(p: A) -> ::std::io::Result { - if let Some(parent) = p.as_ref().parent() { - debug!("Implicitely creating directory: {:?}", parent); - if let Err(e) = create_dir_all(parent) { - return Err(e); - } - } - OpenOptions::new().write(true).read(true).create(true).open(p) - } - -} - -mod inmemory { - use error::StoreError as SE; - use error::StoreErrorKind as SEK; - use std::io::Read; - use std::io::Cursor; - use std::path::PathBuf; - use std::collections::HashMap; - use std::sync::Mutex; - use std::cell::RefCell; - use std::sync::Arc; - - use libimagerror::into::IntoError; - - use super::FileAbstraction; - use super::FileAbstractionInstance; - use error::MapErrInto; - - type Backend = Arc>>>>>; - - /// `FileAbstraction` type, this is the Test version! - /// - /// A lazy file is either absent, but a path to it is available, or it is present. - #[derive(Debug)] - pub struct InMemoryFileAbstractionInstance { - fs_abstraction: Backend, - absent_path: PathBuf, - } - - impl InMemoryFileAbstractionInstance { - - pub fn new(fs: Backend, pb: PathBuf) -> InMemoryFileAbstractionInstance { - InMemoryFileAbstractionInstance { - fs_abstraction: fs, - absent_path: pb - } - } - - } - - impl FileAbstractionInstance for InMemoryFileAbstractionInstance { - - /** - * Get the mutable file behind a InMemoryFileAbstraction object - */ - fn get_file_content(&mut self) -> Result { - debug!("Getting lazy file: {:?}", self); - - let p = self.absent_path.clone(); - match self.fs_abstraction.lock() { - Ok(mut mtx) => { - mtx.get_mut() - .get_mut(&p) - .ok_or(SEK::FileNotFound.into_error()) - .and_then(|t| { - let mut s = String::new(); - t.read_to_string(&mut s) - .map_err_into(SEK::IoError) - .map(|_| s) - }) - } - - Err(_) => Err(SEK::LockError.into_error()) - } - } - - fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE> { - match *self { - InMemoryFileAbstractionInstance { ref absent_path, .. } => { - let mut mtx = self.fs_abstraction.lock().expect("Locking Mutex failed"); - let mut backend = mtx.get_mut(); - - if let Some(ref mut cur) = backend.get_mut(absent_path) { - let mut vec = cur.get_mut(); - vec.clear(); - vec.extend_from_slice(buf); - return Ok(()); - } - let vec = Vec::from(buf); - backend.insert(absent_path.clone(), Cursor::new(vec)); - return Ok(()); - }, - }; - } - } - - #[derive(Debug)] - pub struct InMemoryFileAbstraction { - virtual_filesystem: Backend, - } - - impl InMemoryFileAbstraction { - - pub fn new() -> InMemoryFileAbstraction { - InMemoryFileAbstraction { - virtual_filesystem: Arc::new(Mutex::new(RefCell::new(HashMap::new()))), - } - } - - pub fn backend(&self) -> &Backend { - &self.virtual_filesystem - } - - } - - impl FileAbstraction for InMemoryFileAbstraction { - - fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { - debug!("Removing: {:?}", path); - self.backend() - .lock() - .expect("Locking Mutex failed") - .get_mut() - .remove(path) - .map(|_| ()) - .ok_or(SEK::FileNotFound.into_error()) - } - - fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - debug!("Copying : {:?} -> {:?}", from, to); - let mut mtx = self.backend().lock().expect("Locking Mutex failed"); - let mut backend = mtx.get_mut(); - - let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error())); - backend.insert(to.clone(), a); - debug!("Copying: {:?} -> {:?} worked", from, to); - Ok(()) - } - - fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - debug!("Renaming: {:?} -> {:?}", from, to); - let mut mtx = self.backend().lock().expect("Locking Mutex failed"); - let mut backend = mtx.get_mut(); - - let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error())); - backend.insert(to.clone(), a); - debug!("Renaming: {:?} -> {:?} worked", from, to); - Ok(()) - } - - fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE> { - Ok(()) - } - - fn new_instance(&self, p: PathBuf) -> Box { - Box::new(InMemoryFileAbstractionInstance::new(self.backend().clone(), p)) - } - } - -} - -mod stdio { - use std::cell::RefCell; - use std::collections::HashMap; - use std::fmt::Debug; - use std::fmt::Error as FmtError; - use std::fmt::Formatter; - use std::io::Cursor; - use std::io::{Read, Write}; - use std::path::PathBuf; - use std::sync::Arc; - use std::sync::Mutex; - - use libimagerror::into::IntoError; - use libimagerror::trace::*; - - use error::StoreErrorKind as SEK; - use error::StoreError as SE; - use super::FileAbstraction; - use super::FileAbstractionInstance; - use super::InMemoryFileAbstraction; - - // Because this is not exported in super::inmemory; - type Backend = Arc>>>>>; - - mod mapper { - use std::collections::HashMap; - use std::io::Cursor; - use std::io::{Read, Write}; - use std::path::PathBuf; - use store::Result; - - pub trait Mapper { - fn read_to_fs(&self, Box, &mut HashMap>>) -> Result<()>; - fn fs_to_write(&self, &mut HashMap>>, &mut Write) -> Result<()>; - } - } - - use self::mapper::Mapper; - - pub struct StdIoFileAbstraction { - mapper: M, - mem: InMemoryFileAbstraction, - out: Box, - } - - impl Debug for StdIoFileAbstraction - where M: Mapper - { - fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { - write!(f, "StdIoFileAbstraction({:?}", self.mem) - } - } - - impl StdIoFileAbstraction - where M: Mapper - { - - pub fn new(in_stream: Box, out_stream: Box, mapper: M) -> Result, SE> { - let mem = InMemoryFileAbstraction::new(); - - { - let fill_res = match mem.backend().lock() { - Err(_) => Err(SEK::LockError.into_error()), - Ok(mut mtx) => mapper.read_to_fs(in_stream, mtx.get_mut()) - }; - let _ = try!(fill_res); - } - - Ok(StdIoFileAbstraction { - mapper: mapper, - mem: mem, - out: out_stream, - }) - } - - pub fn backend(&self) -> &Backend { - &self.mem.backend() - } - - } - - impl Drop for StdIoFileAbstraction - where M: Mapper - { - fn drop(&mut self) { - let fill_res = match self.mem.backend().lock() { - Err(_) => Err(SEK::LockError.into_error()), - Ok(mut mtx) => self.mapper.fs_to_write(mtx.get_mut(), &mut *self.out) - }; - - // We can do nothing but end this here with a trace. - // As this drop gets called when imag almost exits, there is no point in exit()ing here - // again. - let _ = fill_res.map_err_trace(); - } - } - - impl FileAbstraction for StdIoFileAbstraction { - - fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { - self.mem.remove_file(path) - } - - fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - self.mem.copy(from, to) - } - - fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { - self.mem.rename(from, to) - } - - fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> { - self.mem.create_dir_all(pb) - } - - fn new_instance(&self, p: PathBuf) -> Box { - self.mem.new_instance(p) - } - } - -} - -#[cfg(test)] -mod test { - use super::FileAbstractionInstance; - use super::inmemory::InMemoryFileAbstraction; - use super::inmemory::InMemoryFileAbstractionInstance; - use std::path::PathBuf; - - #[test] - fn lazy_file() { - let fs = InMemoryFileAbstraction::new(); - - let mut path = PathBuf::from("/tests"); - path.set_file_name("test1"); - let mut lf = InMemoryFileAbstractionInstance::new(fs.backend().clone(), path); - lf.write_file_content(b"Hello World").unwrap(); - let bah = lf.get_file_content().unwrap(); - assert_eq!(bah, "Hello World"); - } - -} diff --git a/libimagstore/src/file_abstraction/fs.rs b/libimagstore/src/file_abstraction/fs.rs new file mode 100644 index 00000000..4210a2e5 --- /dev/null +++ b/libimagstore/src/file_abstraction/fs.rs @@ -0,0 +1,142 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::fs::{File, OpenOptions, create_dir_all, remove_file, copy, rename}; +use std::io::{Seek, SeekFrom, Read}; +use std::path::{Path, PathBuf}; + +use error::{MapErrInto, StoreError as SE, StoreErrorKind as SEK}; + +use super::FileAbstraction; +use super::FileAbstractionInstance; + +#[derive(Debug)] +pub enum FSFileAbstractionInstance { + Absent(PathBuf), + File(File, PathBuf) +} + +impl FileAbstractionInstance for FSFileAbstractionInstance { + + /** + * Get the content behind this file + */ + fn get_file_content(&mut self) -> Result { + debug!("Getting lazy file: {:?}", self); + let (file, path) = match *self { + FSFileAbstractionInstance::File(ref mut f, _) => return { + // We seek to the beginning of the file since we expect each + // access to the file to be in a different context + try!(f.seek(SeekFrom::Start(0)) + .map_err_into(SEK::FileNotSeeked)); + + let mut s = String::new(); + f.read_to_string(&mut s) + .map_err_into(SEK::IoError) + .map(|_| s) + }, + FSFileAbstractionInstance::Absent(ref p) => + (try!(open_file(p).map_err_into(SEK::FileNotFound)), p.clone()), + }; + *self = FSFileAbstractionInstance::File(file, path); + if let FSFileAbstractionInstance::File(ref mut f, _) = *self { + let mut s = String::new(); + f.read_to_string(&mut s) + .map_err_into(SEK::IoError) + .map(|_| s) + } else { + unreachable!() + } + } + + /** + * Write the content of this file + */ + fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE> { + use std::io::Write; + let (file, path) = match *self { + FSFileAbstractionInstance::File(ref mut f, _) => return { + // We seek to the beginning of the file since we expect each + // access to the file to be in a different context + try!(f.seek(SeekFrom::Start(0)) + .map_err_into(SEK::FileNotCreated)); + f.write_all(buf).map_err_into(SEK::FileNotWritten) + }, + FSFileAbstractionInstance::Absent(ref p) => + (try!(create_file(p).map_err_into(SEK::FileNotCreated)), p.clone()), + }; + *self = FSFileAbstractionInstance::File(file, path); + if let FSFileAbstractionInstance::File(ref mut f, _) = *self { + return f.write_all(buf).map_err_into(SEK::FileNotWritten); + } + unreachable!(); + } + +} + +/// `FSFileAbstraction` state type +/// +/// A lazy file is either absent, but a path to it is available, or it is present. +#[derive(Debug)] +pub struct FSFileAbstraction { +} + +impl FSFileAbstraction { + pub fn new() -> FSFileAbstraction { + FSFileAbstraction { } + } +} + +impl FileAbstraction for FSFileAbstraction { + + fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { + remove_file(path).map_err_into(SEK::FileNotRemoved) + } + + fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + copy(from, to).map_err_into(SEK::FileNotCopied).map(|_| ()) + } + + fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + rename(from, to).map_err_into(SEK::FileNotRenamed) + } + + fn create_dir_all(&self, path: &PathBuf) -> Result<(), SE> { + create_dir_all(path).map_err_into(SEK::DirNotCreated) + } + + fn new_instance(&self, p: PathBuf) -> Box { + Box::new(FSFileAbstractionInstance::Absent(p)) + } +} + +fn open_file>(p: A) -> ::std::io::Result { + OpenOptions::new().write(true).read(true).open(p) +} + +fn create_file>(p: A) -> ::std::io::Result { + if let Some(parent) = p.as_ref().parent() { + debug!("Implicitely creating directory: {:?}", parent); + if let Err(e) = create_dir_all(parent) { + return Err(e); + } + } + OpenOptions::new().write(true).read(true).create(true).open(p) +} + diff --git a/libimagstore/src/file_abstraction/inmemory.rs b/libimagstore/src/file_abstraction/inmemory.rs new file mode 100644 index 00000000..c93e3679 --- /dev/null +++ b/libimagstore/src/file_abstraction/inmemory.rs @@ -0,0 +1,166 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use error::StoreError as SE; +use error::StoreErrorKind as SEK; +use std::io::Read; +use std::io::Cursor; +use std::path::PathBuf; +use std::collections::HashMap; +use std::sync::Mutex; +use std::cell::RefCell; +use std::sync::Arc; + +use libimagerror::into::IntoError; + +use super::FileAbstraction; +use super::FileAbstractionInstance; +use error::MapErrInto; + +type Backend = Arc>>>>>; + +/// `FileAbstraction` type, this is the Test version! +/// +/// A lazy file is either absent, but a path to it is available, or it is present. +#[derive(Debug)] +pub struct InMemoryFileAbstractionInstance { + fs_abstraction: Backend, + absent_path: PathBuf, +} + +impl InMemoryFileAbstractionInstance { + + pub fn new(fs: Backend, pb: PathBuf) -> InMemoryFileAbstractionInstance { + InMemoryFileAbstractionInstance { + fs_abstraction: fs, + absent_path: pb + } + } + +} + +impl FileAbstractionInstance for InMemoryFileAbstractionInstance { + + /** + * Get the mutable file behind a InMemoryFileAbstraction object + */ + fn get_file_content(&mut self) -> Result { + debug!("Getting lazy file: {:?}", self); + + let p = self.absent_path.clone(); + match self.fs_abstraction.lock() { + Ok(mut mtx) => { + mtx.get_mut() + .get_mut(&p) + .ok_or(SEK::FileNotFound.into_error()) + .and_then(|t| { + let mut s = String::new(); + t.read_to_string(&mut s) + .map_err_into(SEK::IoError) + .map(|_| s) + }) + } + + Err(_) => Err(SEK::LockError.into_error()) + } + } + + fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE> { + match *self { + InMemoryFileAbstractionInstance { ref absent_path, .. } => { + let mut mtx = self.fs_abstraction.lock().expect("Locking Mutex failed"); + let mut backend = mtx.get_mut(); + + if let Some(ref mut cur) = backend.get_mut(absent_path) { + let mut vec = cur.get_mut(); + vec.clear(); + vec.extend_from_slice(buf); + return Ok(()); + } + let vec = Vec::from(buf); + backend.insert(absent_path.clone(), Cursor::new(vec)); + return Ok(()); + }, + }; + } +} + +#[derive(Debug)] +pub struct InMemoryFileAbstraction { + virtual_filesystem: Backend, +} + +impl InMemoryFileAbstraction { + + pub fn new() -> InMemoryFileAbstraction { + InMemoryFileAbstraction { + virtual_filesystem: Arc::new(Mutex::new(RefCell::new(HashMap::new()))), + } + } + + pub fn backend(&self) -> &Backend { + &self.virtual_filesystem + } + +} + +impl FileAbstraction for InMemoryFileAbstraction { + + fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { + debug!("Removing: {:?}", path); + self.backend() + .lock() + .expect("Locking Mutex failed") + .get_mut() + .remove(path) + .map(|_| ()) + .ok_or(SEK::FileNotFound.into_error()) + } + + fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + debug!("Copying : {:?} -> {:?}", from, to); + let mut mtx = self.backend().lock().expect("Locking Mutex failed"); + let mut backend = mtx.get_mut(); + + let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error())); + backend.insert(to.clone(), a); + debug!("Copying: {:?} -> {:?} worked", from, to); + Ok(()) + } + + fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + debug!("Renaming: {:?} -> {:?}", from, to); + let mut mtx = self.backend().lock().expect("Locking Mutex failed"); + let mut backend = mtx.get_mut(); + + let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error())); + backend.insert(to.clone(), a); + debug!("Renaming: {:?} -> {:?} worked", from, to); + Ok(()) + } + + fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE> { + Ok(()) + } + + fn new_instance(&self, p: PathBuf) -> Box { + Box::new(InMemoryFileAbstractionInstance::new(self.backend().clone(), p)) + } +} + diff --git a/libimagstore/src/file_abstraction/mod.rs b/libimagstore/src/file_abstraction/mod.rs new file mode 100644 index 00000000..6b98c83d --- /dev/null +++ b/libimagstore/src/file_abstraction/mod.rs @@ -0,0 +1,126 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +//! The filesystem abstraction code +//! +//! # Problem +//! +//! First, we had a compiletime backend for the store. This means that the actual filesystem +//! operations were compiled into the store either as real filesystem operations (in a normal debug +//! or release build) but as a in-memory variant in the 'test' case. +//! So tests did not hit the filesystem when running. +//! This gave us us the possibility to run tests concurrently with multiple +//! stores that did not interfere with eachother. +//! +//! This approach worked perfectly well until we started to test not the +//! store itself but crates that depend on the store implementation. +//! When running tests in a crate that depends on the store, the store +//! itself was compiled with the filesystem-hitting-backend. +//! This was problematic, as tests could not be implemented without hitting +//! the filesystem. +//! +//! Hence we implemented this. +//! +//! # Implementation +//! +//! The filesystem is abstracted via a trait `FileAbstraction` which +//! contains the essential functions for working with the filesystem. +//! +//! Two implementations are provided in the code: +//! +//! * FSFileAbstraction +//! * InMemoryFileAbstraction +//! +//! whereas the first actually works with the filesystem and the latter +//! works with an in-memory HashMap that is used as filesystem. +//! +//! Further, the trait `FileAbstractionInstance` was introduced for +//! functions which are executed on actual instances of content from the +//! filesystem, which was previousely tied into the general abstraction +//! mechanism. +//! +//! So, the `FileAbstraction` trait is for working with the filesystem, the +//! `FileAbstractionInstance` trait is for working with instances of content +//! from the filesystem (speak: actual Files). +//! +//! In case of the `FSFileAbstractionInstance`, which is the implementation +//! of the `FileAbstractionInstance` for the actual filesystem-hitting code, +//! the underlying resource is managed like with the old code before. +//! The `InMemoryFileAbstractionInstance` implementation is corrosponding to +//! the `InMemoryFileAbstraction` implementation - for the in-memory +//! "filesystem". +//! +//! The implementation of the `get_file_content()` function had to be +//! changed to return a `String` rather than a `&mut Read` because of +//! lifetime issues. +//! This change is store-internally and the API of the store itself was not +//! affected. +//! + +use std::path::PathBuf; +use std::fmt::Debug; + +use error::StoreError as SE; + +pub use self::fs::FSFileAbstraction; +pub use self::fs::FSFileAbstractionInstance; +pub use self::inmemory::InMemoryFileAbstraction; +pub use self::inmemory::InMemoryFileAbstractionInstance; + +/// An abstraction trait over filesystem actions +pub trait FileAbstraction : Debug { + fn remove_file(&self, path: &PathBuf) -> Result<(), SE>; + fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>; + fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>; + fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE>; + + fn new_instance(&self, p: PathBuf) -> Box; +} + +/// An abstraction trait over actions on files +pub trait FileAbstractionInstance : Debug { + fn get_file_content(&mut self) -> Result; + fn write_file_content(&mut self, buf: &[u8]) -> Result<(), SE>; +} + +mod fs; +mod inmemory; +mod stdio; + +#[cfg(test)] +mod test { + use super::FileAbstractionInstance; + use super::inmemory::InMemoryFileAbstraction; + use super::inmemory::InMemoryFileAbstractionInstance; + use std::path::PathBuf; + + #[test] + fn lazy_file() { + let fs = InMemoryFileAbstraction::new(); + + let mut path = PathBuf::from("/tests"); + path.set_file_name("test1"); + let mut lf = InMemoryFileAbstractionInstance::new(fs.backend().clone(), path); + lf.write_file_content(b"Hello World").unwrap(); + let bah = lf.get_file_content().unwrap(); + assert_eq!(bah, "Hello World"); + } + +} + diff --git a/libimagstore/src/file_abstraction/stdio/mapper/mod.rs b/libimagstore/src/file_abstraction/stdio/mapper/mod.rs new file mode 100644 index 00000000..cdc40121 --- /dev/null +++ b/libimagstore/src/file_abstraction/stdio/mapper/mod.rs @@ -0,0 +1,30 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::collections::HashMap; +use std::io::Cursor; +use std::io::{Read, Write}; +use std::path::PathBuf; +use store::Result; + +pub trait Mapper { + fn read_to_fs(&self, Box, &mut HashMap>>) -> Result<()>; + fn fs_to_write(&self, &mut HashMap>>, &mut Write) -> Result<()>; +} + diff --git a/libimagstore/src/file_abstraction/stdio/mod.rs b/libimagstore/src/file_abstraction/stdio/mod.rs new file mode 100644 index 00000000..c6b7c671 --- /dev/null +++ b/libimagstore/src/file_abstraction/stdio/mod.rs @@ -0,0 +1,127 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::Debug; +use std::fmt::Error as FmtError; +use std::fmt::Formatter; +use std::io::Cursor; +use std::io::{Read, Write}; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; + +use libimagerror::into::IntoError; +use libimagerror::trace::*; + +use error::StoreErrorKind as SEK; +use error::StoreError as SE; +use super::FileAbstraction; +use super::FileAbstractionInstance; +use super::InMemoryFileAbstraction; + +pub mod mapper; +use self::mapper::Mapper; + +// Because this is not exported in super::inmemory; +type Backend = Arc>>>>>; + +pub struct StdIoFileAbstraction { + mapper: M, + mem: InMemoryFileAbstraction, + out: Box, +} + +impl Debug for StdIoFileAbstraction + where M: Mapper +{ + fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { + write!(f, "StdIoFileAbstraction({:?}", self.mem) + } +} + +impl StdIoFileAbstraction + where M: Mapper +{ + + pub fn new(in_stream: Box, out_stream: Box, mapper: M) -> Result, SE> { + let mem = InMemoryFileAbstraction::new(); + + { + let fill_res = match mem.backend().lock() { + Err(_) => Err(SEK::LockError.into_error()), + Ok(mut mtx) => mapper.read_to_fs(in_stream, mtx.get_mut()) + }; + let _ = try!(fill_res); + } + + Ok(StdIoFileAbstraction { + mapper: mapper, + mem: mem, + out: out_stream, + }) + } + + pub fn backend(&self) -> &Backend { + &self.mem.backend() + } + +} + +impl Drop for StdIoFileAbstraction + where M: Mapper +{ + fn drop(&mut self) { + let fill_res = match self.mem.backend().lock() { + Err(_) => Err(SEK::LockError.into_error()), + Ok(mut mtx) => self.mapper.fs_to_write(mtx.get_mut(), &mut *self.out) + }; + + // We can do nothing but end this here with a trace. + // As this drop gets called when imag almost exits, there is no point in exit()ing here + // again. + let _ = fill_res.map_err_trace(); + } +} + +impl FileAbstraction for StdIoFileAbstraction { + + fn remove_file(&self, path: &PathBuf) -> Result<(), SE> { + self.mem.remove_file(path) + } + + fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + self.mem.copy(from, to) + } + + fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> { + self.mem.rename(from, to) + } + + fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> { + self.mem.create_dir_all(pb) + } + + fn new_instance(&self, p: PathBuf) -> Box { + self.mem.new_instance(p) + } +} + +