Merge branch 'storage' into master

This commit is contained in:
Matthias Beyer 2015-12-02 12:07:40 +01:00
commit 78aeddb42e
4 changed files with 310 additions and 108 deletions

View file

@ -6,8 +6,11 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::vec::Vec; use std::vec::Vec;
use std::fs::File as FSFile; use std::fs::File as FSFile;
use std::fs::create_dir_all;
use std::fs::remove_file;
use std::io::Read; use std::io::Read;
use std::io::Write; use std::io::Write;
use std::vec::IntoIter;
use glob::glob; use glob::glob;
use glob::Paths; use glob::Paths;
@ -19,18 +22,34 @@ use storage::parser::{FileHeaderParser, Parser, ParserError};
use module::Module; use module::Module;
use runtime::Runtime; use runtime::Runtime;
pub type BackendOperationResult = Result<(), StorageBackendError>; pub type BackendOperationResult<T = ()> = Result<T, StorageBackendError>;
pub struct StorageBackend { pub struct StorageBackend {
basepath: String, basepath: String,
storepath: String,
} }
impl StorageBackend { impl StorageBackend {
pub fn new(basepath: String) -> StorageBackend { pub fn new(rt: &Runtime) -> BackendOperationResult<StorageBackend> {
StorageBackend { let storepath = rt.get_rtp() + "/store/";
basepath: basepath, 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<M: Module>(rt: &Runtime, m: &M) -> StorageBackend { fn build<M: Module>(rt: &Runtime, m: &M) -> StorageBackend {
@ -40,9 +59,8 @@ impl StorageBackend {
StorageBackend::new(path) StorageBackend::new(path)
} }
fn get_file_ids(&self) -> Option<Vec<FileID>> { fn get_file_ids(&self, m: &Module) -> Option<Vec<FileID>> {
debug!("Getting files from {}", self.basepath); let list = glob(&self.prefix_of_files_for_module(m)[..]);
let list = glob(&self.basepath[..]);
if let Ok(globlist) = list { if let Ok(globlist) = list {
let mut v = vec![]; let mut v = vec![];
@ -61,69 +79,107 @@ impl StorageBackend {
} }
} }
pub fn iter_ids(&self, m: &Module) -> Option<IntoIter<FileID>>
{
glob(&self.prefix_of_files_for_module(m)[..]).and_then(|globlist| {
let v = globlist.filter_map(Result::ok)
.map(|pbuf| from_pathbuf(&pbuf))
.collect::<Vec<FileID>>()
.into_iter();
Ok(v)
}).ok()
}
pub fn iter_files<'a, HP>(&self, m: &'a Module, p: &Parser<HP>)
-> Option<IntoIter<File<'a>>>
where HP: FileHeaderParser
{
self.iter_ids(m).and_then(|ids| {
Some(ids.filter_map(|id| self.get_file_by_id(m, &id, p))
.collect::<Vec<File>>()
.into_iter())
})
}
/* /*
* Write a file to disk. * Write a file to disk.
* *
* The file is moved to this function as the file won't be edited afterwards * The file is moved to this function as the file won't be edited afterwards
*/ */
pub fn put_file<'a, HP>(&self, f: File, p: &Parser<HP>) -> pub fn put_file<HP>(&self, f: File, p: &Parser<HP>) -> BackendOperationResult
Result<BackendOperationResult, ParserError> where HP: FileHeaderParser
where HP: FileHeaderParser<'a>
{ {
let written = p.write(f.contents()); let written = write_with_parser(&f, p);
if let Ok(string) = written { if written.is_err() { return Err(written.err().unwrap()); }
let string = written.unwrap();
let path = self.build_filepath(&f); let path = self.build_filepath(&f);
debug!("Writing file: {}", path); debug!("Writing file: {}", path);
debug!(" contents: {}", string); debug!(" string: {}", string);
Ok(Ok(()))
} else { FSFile::create(&path).map(|mut file| {
debug!("Error parsing : {:?}", f.contents()); debug!("Created file at '{}'", path);
Err(written.err().unwrap()) 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 * 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 * then replace its contents with the contents of the passed file object
*/ */
pub fn update_file<'a, HP>(&self, f: File, p: &Parser<HP>) pub fn update_file<HP>(&self, f: File, p: &Parser<HP>) -> BackendOperationResult
-> Result<BackendOperationResult, ParserError> where HP: FileHeaderParser
where HP: FileHeaderParser<'a>
{ {
let contents = p.write(f.contents()); let contents = write_with_parser(&f, p);
if contents.is_err() { return Err(contents.err().unwrap()); }
if contents.is_err() { let string = contents.unwrap();
debug!("Error parsing contents: {:?}", f.contents());
return Err(contents.err().unwrap());
}
let content = contents.unwrap();
debug!("Success parsing content : {}", content);
let path = self.build_filepath(&f); let path = self.build_filepath(&f);
debug!("Trying to write to file at {}", path); debug!("Writing file: {}", path);
if let Err(_) = FSFile::open(&path) { debug!(" string: {}", string);
debug!("Error opening {}", path);
return Ok(Err(StorageBackendError::new(
String::from("File::open()"),
format!("Tried to open '{}'", path),
String::from("Tried to update contents of this file, though file doesn't exist"),
None)))
}
if let Ok(mut file) = FSFile::create(&path) { FSFile::open(&path).map(|mut file| {
if let Err(writeerr) = file.write_all(&content.clone().into_bytes()) { debug!("Open file at '{}'", path);
debug!("Error writing to {}", path); file.write_all(&string.clone().into_bytes())
return Ok(Err(StorageBackendError::new( .map_err(|ioerr| {
String::from("File::write()"), debug!("Could not write file");
format!("Tried to write '{}'", path), let mut err = StorageBackendError::build(
String::from("Tried to write contents of this file, though operation did not succeed"), "File::write()",
Some(content)))) "Tried to write contents of this file, though operation did not succeed",
} Some(string)
} );
err.caused_by = Some(Box::new(ioerr));
debug!("Successfully written to file."); err
Ok(Ok(())) })
}).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(()))
} }
/* /*
@ -133,29 +189,57 @@ impl StorageBackend {
* TODO: Needs refactoring, as there might be an error when reading from * TODO: Needs refactoring, as there might be an error when reading from
* disk OR the id just does not exist. * disk OR the id just does not exist.
*/ */
pub fn get_file_by_id<'a, HP>(&self, id: FileID, p: &Parser<HP>) -> Option<File> pub fn get_file_by_id<'a, HP>(&self, m: &'a Module, id: &FileID, p: &Parser<HP>) -> Option<File<'a>>
where HP: FileHeaderParser<'a> where HP: FileHeaderParser
{ {
debug!("Searching for file with id '{}'", id); debug!("Searching for file with id '{}'", id);
if let Ok(mut fs) = FSFile::open(self.build_filepath_with_id(id.clone())) { if let Ok(mut fs) = FSFile::open(self.build_filepath_with_id(m, id.clone())) {
let mut s = String::new(); let mut s = String::new();
fs.read_to_string(&mut s); fs.read_to_string(&mut s);
debug!("Success reading file with id '{}'", id); debug!("Success reading file with id '{}'", id);
debug!("Parsing to internal structure now"); debug!("Parsing to internal structure now");
p.read(s).and_then(|(h, d)| Ok(File::from_parser_result(id, h, d))).ok() p.read(s).and_then(|(h, d)| Ok(File::from_parser_result(m, id.clone(), h, d))).ok()
} else { } else {
debug!("No file with id '{}'", id); debug!("No file with id '{}'", id);
None None
} }
} }
fn build_filepath(&self, f: &File) -> String { pub fn remove_file(&self, m: &Module, file: File, checked: bool) -> BackendOperationResult {
self.build_filepath_with_id(f.id()) if checked {
error!("Checked remove not implemented yet. I will crash now");
unimplemented!()
} }
fn build_filepath_with_id(&self, id: FileID) -> String { debug!("Doing unchecked remove");
debug!("Building filepath for id '{}'", id); info!("Going to remove file: {}", file);
self.basepath.clone() + &id[..]
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()
} }
} }
@ -164,23 +248,35 @@ impl StorageBackend {
pub struct StorageBackendError { pub struct StorageBackendError {
pub action: String, // The file system action in words pub action: String, // The file system action in words
pub desc: String, // A short description pub desc: String, // A short description
pub explanation: String, // A long, user friendly description pub data_dump: Option<String>, // Data dump, if any
pub data_dump: Option<String> // Data dump, if any pub caused_by: Option<Box<Error>>, // caused from this error
} }
impl StorageBackendError { impl StorageBackendError {
fn new(action: String, fn new(action: String,
desc : String, desc : String,
explan: String,
data : Option<String>) -> StorageBackendError data : Option<String>) -> StorageBackendError
{ {
StorageBackendError { StorageBackendError {
action: action, action: action,
desc: desc, desc: desc,
explanation: explan,
data_dump: data, data_dump: data,
caused_by: None,
} }
} }
fn build(action: &'static str,
desc: &'static str,
data : Option<String>) -> StorageBackendError
{
StorageBackendError {
action: String::from(action),
desc: String::from(desc),
dataDump: data,
caused_by: None,
}
}
} }
impl Error for StorageBackendError { impl Error for StorageBackendError {
@ -190,15 +286,30 @@ impl Error for StorageBackendError {
} }
fn cause(&self) -> Option<&Error> { fn cause(&self) -> Option<&Error> {
None self.caused_by.as_ref().map(|e| &**e)
} }
} }
impl Display for StorageBackendError { impl Display for StorageBackendError {
fn fmt(&self, f: &mut Formatter) -> FMTResult { fn fmt(&self, f: &mut Formatter) -> FMTResult {
write!(f, "StorageBackendError[{}]: {}\n\n{}", write!(f, "StorageBackendError[{}]: {}",
self.action, self.desc, self.explanation) self.action, self.desc)
} }
} }
fn write_with_parser<'a, HP>(f: &File, p: &Parser<HP>) -> Result<String, StorageBackendError>
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)
})
}

View file

@ -2,9 +2,12 @@ use std::error::Error;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::fmt; use std::fmt;
use super::parser::FileHeaderParser; use module::Module;
use super::parser::{FileHeaderParser, Parser, ParserError};
use storage::file_id::*; use storage::file_id::*;
use regex::Regex;
#[derive(Debug)] #[derive(Debug)]
pub enum FileHeaderSpec { pub enum FileHeaderSpec {
Null, Null,
@ -27,7 +30,7 @@ pub enum FileHeaderData {
UInteger(u64), UInteger(u64),
Float(f64), Float(f64),
Text(String), Text(String),
Key { name: String, value: Box<FileHeaderData> }, Key { name: &'static str, value: Box<FileHeaderData> },
Map { keys: Vec<FileHeaderData> }, Map { keys: Vec<FileHeaderData> },
Array { values: Box<Vec<FileHeaderData>> }, Array { values: Box<Vec<FileHeaderData>> },
} }
@ -56,6 +59,28 @@ impl Display for FileHeaderSpec {
} }
impl FileHeaderData {
pub fn matches_with(&self, r: &Regex) -> bool {
match self {
&FileHeaderData::Text(ref t) => r.is_match(&t[..]),
&FileHeaderData::Key{name: ref n, value: ref val} => {
r.is_match(n) || val.matches_with(r)
},
&FileHeaderData::Map{keys: ref dks} => {
dks.iter().any(|x| x.matches_with(r))
},
&FileHeaderData::Array{values: ref vs} => {
vs.iter().any(|x| x.matches_with(r))
}
_ => false,
}
}
}
pub struct MatchError<'a> { pub struct MatchError<'a> {
summary: String, summary: String,
expected: &'a FileHeaderSpec, expected: &'a FileHeaderSpec,
@ -176,16 +201,18 @@ pub fn match_header_spec<'a>(spec: &'a FileHeaderSpec, data: &'a FileHeaderData)
* Internal abstract view on a file. Does not exist on the FS and is just kept * Internal abstract view on a file. Does not exist on the FS and is just kept
* internally until it is written to disk. * internally until it is written to disk.
*/ */
pub struct File { pub struct File<'a> {
owning_module : &'a Module,
header : FileHeaderData, header : FileHeaderData,
data : String, data : String,
id : FileID, id : FileID,
} }
impl File { impl<'a> File<'a> {
pub fn new() -> File { pub fn new(module: &'a Module) -> File<'a> {
let f = File { let f = File {
owning_module: module,
header: FileHeaderData::Null, header: FileHeaderData::Null,
data: String::from(""), data: String::from(""),
id: File::get_new_file_id(), id: File::get_new_file_id(),
@ -194,8 +221,9 @@ impl File {
f f
} }
pub fn from_parser_result(id: FileID, header: FileHeaderData, data: String) -> File { pub fn from_parser_result(module: &Module, id: FileID, header: FileHeaderData, data: String) -> File {
let f = File { let f = File {
owning_module: module,
header: header, header: header,
data: data, data: data,
id: id, id: id,
@ -204,8 +232,9 @@ impl File {
f f
} }
pub fn new_with_header(h: FileHeaderData) -> File { pub fn new_with_header(module: &Module, h: FileHeaderData) -> File {
let f = File { let f = File {
owning_module: module,
header: h, header: h,
data: String::from(""), data: String::from(""),
id: File::get_new_file_id(), id: File::get_new_file_id(),
@ -214,8 +243,9 @@ impl File {
f f
} }
pub fn new_with_data(d: String) -> File { pub fn new_with_data(module: &Module, d: String) -> File {
let f = File { let f = File {
owning_module: module,
header: FileHeaderData::Null, header: FileHeaderData::Null,
data: d, data: d,
id: File::get_new_file_id(), id: File::get_new_file_id(),
@ -224,8 +254,9 @@ impl File {
f f
} }
pub fn new_with_content(h: FileHeaderData, d: String) -> File { pub fn new_with_content(module: &Module, h: FileHeaderData, d: String) -> File {
let f = File { let f = File {
owning_module: module,
header: h, header: h,
data: d, data: d,
id: File::get_new_file_id(), id: File::get_new_file_id(),
@ -234,25 +265,66 @@ impl File {
f f
} }
pub fn header(&self) -> FileHeaderData {
self.header.clone()
}
pub fn data(&self) -> String {
self.data.clone()
}
pub fn contents(&self) -> (FileHeaderData, String) { pub fn contents(&self) -> (FileHeaderData, String) {
(self.header.clone(), self.data.clone()) (self.header(), self.data())
} }
pub fn id(&self) -> FileID { pub fn id(&self) -> FileID {
self.id.clone() self.id.clone()
} }
pub fn owner(&self) -> &Module {
self.owning_module
}
pub fn matches_with(&self, r: &Regex) -> bool {
r.is_match(&self.data[..]) || self.header.matches_with(r)
}
fn get_new_file_id() -> FileID { fn get_new_file_id() -> FileID {
use uuid::Uuid; use uuid::Uuid;
Uuid::new_v4().to_hyphenated_string() Uuid::new_v4().to_hyphenated_string()
} }
} }
impl Debug for File { impl<'a> Display for File<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(f, "File[{:?}] header: '{:?}', data: '{:?}')", write!(fmt,
self.id, self.header, self.data) "[File] Owner : '{:?}'
} FileID: '{:?}'
Header: '{:?}'
Data : '{:?}'",
self.owning_module,
self.header,
self.data,
self.id);
Ok(())
} }
}
impl<'a> Debug for File<'a> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt,
"[File] Owner : '{:?}'
FileID: '{:?}'
Header: '{:?}'
Data : '{:?}'",
self.owning_module,
self.header,
self.data,
self.id);
Ok(())
}
}

View file

@ -11,18 +11,22 @@ use super::super::parser::{FileHeaderParser, ParserError};
use super::super::file::{FileHeaderSpec, FileHeaderData}; use super::super::file::{FileHeaderSpec, FileHeaderData};
struct JsonHeaderParser<'a> { pub struct JsonHeaderParser {
spec: &'a FileHeaderSpec, spec: Option<FileHeaderSpec>,
} }
impl<'a> FileHeaderParser<'a> for JsonHeaderParser<'a> { impl JsonHeaderParser {
fn new(spec: &'a FileHeaderSpec) -> JsonHeaderParser<'a> { pub fn new(spec: Option<FileHeaderSpec>) -> JsonHeaderParser {
JsonHeaderParser { JsonHeaderParser {
spec: spec spec: spec
} }
} }
}
impl FileHeaderParser for JsonHeaderParser {
fn read(&self, string: Option<String>) fn read(&self, string: Option<String>)
-> Result<FileHeaderData, ParserError> -> Result<FileHeaderData, ParserError>
{ {
@ -41,9 +45,16 @@ impl<'a> FileHeaderParser<'a> for JsonHeaderParser<'a> {
} }
fn write(&self, data: &FileHeaderData) -> Result<String, ParserError> { fn write(&self, data: &FileHeaderData) -> Result<String, ParserError> {
let mut ser = Serializer::pretty(stdout()); let mut s = Vec::<u8>::new();
{
let mut ser = Serializer::pretty(&mut s);
data.serialize(&mut ser); data.serialize(&mut ser);
Ok(String::from("")) }
String::from_utf8(s).or(
Err(ParserError::short("Cannot parse utf8 bytes",
String::from("<not printable>"),
0)))
} }
} }
@ -63,11 +74,12 @@ fn visit_json(v: &Value) -> FileHeaderData {
} }
}, },
&Value::Object(ref btree) => { &Value::Object(ref btree) => {
let btree = btree.clone();
FileHeaderData::Map{ FileHeaderData::Map{
keys: btree.clone().iter().map(|(k, v)| keys: btree.into_iter().map(|(k, v)|
FileHeaderData::Key { FileHeaderData::Key {
name: k.clone(), name: k,
value: Box::new(visit_json(v)), value: Box::new(visit_json(&v)),
} }
).collect() ).collect()
} }
@ -91,13 +103,22 @@ impl Serialize for FileHeaderData {
&FileHeaderData::Float(ref f) => f.serialize(ser), &FileHeaderData::Float(ref f) => f.serialize(ser),
&FileHeaderData::Text(ref s) => (&s[..]).serialize(ser), &FileHeaderData::Text(ref s) => (&s[..]).serialize(ser),
&FileHeaderData::Array{values: ref vs} => vs.serialize(ser), &FileHeaderData::Array{values: ref vs} => vs.serialize(ser),
&FileHeaderData::Map{keys: ref ks} => ks.serialize(ser), &FileHeaderData::Map{keys: ref ks} => {
&FileHeaderData::Key{name: ref n, value: ref v} => {
let mut hm = HashMap::new(); let mut hm = HashMap::new();
for key in ks {
if let &FileHeaderData::Key{name: ref n, value: ref v} = key {
hm.insert(n, v); hm.insert(n, v);
hm.serialize(ser) } else {
panic!("Not a key: {:?}", key);
} }
} }
hm.serialize(ser)
},
&FileHeaderData::Key{name: ref n, value: ref v} => unreachable!(),
}
} }
} }

View file

@ -73,9 +73,7 @@ impl Display for ParserError {
} }
pub trait FileHeaderParser : Sized {
pub trait FileHeaderParser<'a> : Sized {
fn new(spec: &'a FileHeaderSpec) -> Self;
fn read(&self, string: Option<String>) -> Result<FileHeaderData, ParserError>; fn read(&self, string: Option<String>) -> Result<FileHeaderData, ParserError>;
fn write(&self, data: &FileHeaderData) -> Result<String, ParserError>; fn write(&self, data: &FileHeaderData) -> Result<String, ParserError>;
} }
@ -87,11 +85,11 @@ pub struct Parser<HP>
headerp : HP, headerp : HP,
} }
impl<'a, HP> Parser<HP> where impl<HP> Parser<HP> where
HP: FileHeaderParser<'a>, HP: FileHeaderParser,
{ {
fn new(headerp: HP) -> Parser<HP> { pub fn new(headerp: HP) -> Parser<HP> {
Parser { Parser {
headerp: headerp, headerp: headerp,
} }