use std::error::Error; use std::fmt::Display; use std::fmt::Formatter; use std::fmt::Result as FMTResult; use std::path::Path; use std::path::PathBuf; use std::vec::Vec; use std::fs::File as FSFile; use std::fs::create_dir_all; use std::fs::remove_file; use std::io::Read; use std::io::Write; use std::vec::IntoIter; use glob::glob; use glob::Paths; use storage::file::File; use storage::file_id::*; use storage::parser::{FileHeaderParser, Parser, ParserError}; use module::Module; use runtime::Runtime; pub type BackendOperationResult = Result; pub struct StorageBackend { basepath: String, storepath: String, } impl StorageBackend { pub fn new(rt: &Runtime) -> BackendOperationResult { let storepath = rt.get_rtp() + "/store/"; debug!("Trying to create {}", storepath); create_dir_all(&storepath).and_then(|_| { debug!("Creating succeeded, constructing backend instance"); Ok(StorageBackend { basepath: rt.get_rtp(), storepath: storepath.clone(), }) }).or_else(|e| { debug!("Creating failed, constructing error instance"); let mut serr = StorageBackendError::build( "create_dir_all()", "Could not create store directories", Some(storepath) ); serr.caused_by = Some(Box::new(e)); Err(serr) }) } fn build(rt: &Runtime, m: &M) -> StorageBackend { let path = rt.get_rtp() + m.name() + "/store"; // TODO: Don't use "/store" but value from configuration debug!("Building StorageBackend for {}", path); StorageBackend::new(path) } fn get_file_ids(&self, m: &Module) -> Option> { let list = glob(&self.prefix_of_files_for_module(m)[..]); if let Ok(globlist) = list { let mut v = vec![]; for entry in globlist { if let Ok(path) = entry { debug!(" - File: {:?}", path); v.push(from_pathbuf(&path)); } else { // Entry is not a path } } Some(v) } else { None } } pub fn iter_ids(&self, m: &Module) -> Option> { glob(&self.prefix_of_files_for_module(m)[..]).and_then(|globlist| { let v = globlist.filter_map(Result::ok) .map(|pbuf| from_pathbuf(&pbuf)) .collect::>() .into_iter(); Ok(v) }).ok() } pub fn iter_files<'a, HP>(&self, m: &'a Module, p: &Parser) -> Option>> where HP: FileHeaderParser { self.iter_ids(m).and_then(|ids| { Some(ids.filter_map(|id| self.get_file_by_id(m, &id, p)) .collect::>() .into_iter()) }) } /* * Write a file to disk. * * The file is moved to this function as the file won't be edited afterwards */ pub fn put_file(&self, f: File, p: &Parser) -> BackendOperationResult where HP: FileHeaderParser { let written = write_with_parser(&f, p); if written.is_err() { return Err(written.err().unwrap()); } let string = written.unwrap(); let path = self.build_filepath(&f); debug!("Writing file: {}", path); debug!(" string: {}", string); FSFile::create(&path).map(|mut file| { debug!("Created file at '{}'", path); file.write_all(&string.clone().into_bytes()) .map_err(|ioerr| { debug!("Could not write file"); let mut err = StorageBackendError::build( "File::write_all()", "Could not write out File contents", None ); err.caused_by = Some(Box::new(ioerr)); err }) }).map_err(|writeerr| { debug!("Could not create file at '{}'", path); let mut err = StorageBackendError::build( "File::create()", "Creating file on disk failed", None ); err.caused_by = Some(Box::new(writeerr)); err }).and(Ok(())) } /* * Update a file. We have the UUID and can find the file on FS with it and * then replace its contents with the contents of the passed file object */ pub fn update_file(&self, f: File, p: &Parser) -> BackendOperationResult where HP: FileHeaderParser { let contents = write_with_parser(&f, p); if contents.is_err() { return Err(contents.err().unwrap()); } let string = contents.unwrap(); let path = self.build_filepath(&f); debug!("Writing file: {}", path); debug!(" string: {}", string); FSFile::open(&path).map(|mut file| { debug!("Open file at '{}'", path); file.write_all(&string.clone().into_bytes()) .map_err(|ioerr| { debug!("Could not write file"); let mut err = StorageBackendError::build( "File::write()", "Tried to write contents of this file, though operation did not succeed", Some(string) ); err.caused_by = Some(Box::new(ioerr)); err }) }).map_err(|writeerr| { debug!("Could not write file at '{}'", path); let mut err = StorageBackendError::build( "File::open()", "Tried to update contents of this file, though file doesn't exist", None ); err.caused_by = Some(Box::new(writeerr)); err }).and(Ok(())) } /* * Find a file by its ID and return it if found. Return nothing if not * found, of course. * * TODO: Needs refactoring, as there might be an error when reading from * disk OR the id just does not exist. */ pub fn get_file_by_id(&self, id: FileID, p: &Parser) -> Option where HP: FileHeaderParser { debug!("Searching for file with id '{}'", id); if let Ok(mut fs) = FSFile::open(self.build_filepath_with_id(m, id.clone())) { let mut s = String::new(); fs.read_to_string(&mut s); debug!("Success reading file with id '{}'", id); debug!("Parsing to internal structure now"); p.read(s).and_then(|(h, d)| Ok(File::from_parser_result(m, id.clone(), h, d))).ok() } else { debug!("No file with id '{}'", id); None } } pub fn remove_file(&self, m: &Module, file: File, checked: bool) -> BackendOperationResult { if checked { error!("Checked remove not implemented yet. I will crash now"); unimplemented!() } debug!("Doing unchecked remove"); info!("Going to remove file: {}", file); let fp = self.build_filepath(&file); remove_file(fp).map_err(|e| { let mut serr = StorageBackendError::build( "remove_file()", "File removal failed", Some(format!("{}", file)) ); serr.caused_by = Some(Box::new(e)); serr }) } fn build_filepath(&self, f: &File) -> String { self.build_filepath_with_id(f.owner(), f.id()) } fn build_filepath_with_id(&self, owner: &Module, id: FileID) -> String { debug!("Building filepath with id"); debug!(" basepath: '{}'", self.basepath); debug!(" storepath: '{}'", self.storepath); debug!(" id : '{}'", id); self.prefix_of_files_for_module(owner) + "-" + &id[..] + ".imag" } fn prefix_of_files_for_module(&self, m: &Module) -> String { self.storepath.clone() + m.name() } } #[derive(Debug)] pub struct StorageBackendError { pub action: String, // The file system action in words pub desc: String, // A short description pub data_dump: Option, // Data dump, if any pub caused_by: Option>, // caused from this error } impl StorageBackendError { fn new(action: String, desc : String, data : Option) -> StorageBackendError { StorageBackendError { action: action, desc: desc, data_dump: data, caused_by: None, } } fn build(action: &'static str, desc: &'static str, data : Option) -> StorageBackendError { StorageBackendError { action: String::from(action), desc: String::from(desc), dataDump: data, caused_by: None, } } } impl Error for StorageBackendError { fn description(&self) -> &str { &self.desc[..] } fn cause(&self) -> Option<&Error> { self.caused_by.as_ref().map(|e| &**e) } } impl Display for StorageBackendError { fn fmt(&self, f: &mut Formatter) -> FMTResult { write!(f, "StorageBackendError[{}]: {}", self.action, self.desc) } } fn write_with_parser<'a, HP>(f: &File, p: &Parser) -> Result where HP: FileHeaderParser { p.write(f.contents()) .or_else(|err| { let mut serr = StorageBackendError::build( "Parser::write()", "Cannot translate internal representation of file contents into on-disk representation", None ); serr.caused_by = Some(Box::new(err)); Err(serr) }) }