Merge branch 'bootstrap-storage-backends'

I guess this merge only happens because the branch gets way too messy.

Further work on the storage backend will happen.
This commit is contained in:
Matthias Beyer 2015-11-24 19:42:07 +01:00
commit fe0ef09417
14 changed files with 305 additions and 62 deletions

6
Cargo.lock generated
View file

@ -5,6 +5,7 @@ dependencies = [
"chrono 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "clap 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"rustty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "rustty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -71,6 +72,11 @@ dependencies = [
"nom 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", "nom 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "glob"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "kernel32-sys" name = "kernel32-sys"
version = "0.1.4" version = "0.1.4"

View file

@ -12,6 +12,7 @@ log = "0.3.2"
regex = "0.1.41" regex = "0.1.41"
url = "0.2.37" url = "0.2.37"
uuid = "0.1.18" uuid = "0.1.18"
glob = "0.2.10"
config = "0.1.2" config = "0.1.2"

View file

@ -33,5 +33,9 @@ impl<'a> CliConfig<'a> {
pub fn is_debugging(&self) -> bool { pub fn is_debugging(&self) -> bool {
self.cli_matches.is_present("debug") self.cli_matches.is_present("debug")
} }
pub fn get_rtp(&self) -> Option<String> {
self.cli_matches.value_of("rtp").and_then(|s| Some(String::from(s)))
}
} }

View file

@ -57,6 +57,10 @@ impl Configuration {
format!("{}{}", self.rtp, self.store_sub) format!("{}{}", self.rtp, self.store_sub)
} }
pub fn get_rtp(&self) -> String {
self.rtp.clone()
}
} }
fn rtp_path(config: &CliConfig) -> String { fn rtp_path(config: &CliConfig) -> String {

View file

@ -2,6 +2,8 @@
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate serde; #[macro_use] extern crate serde;
#[macro_use] extern crate serde_json; #[macro_use] extern crate serde_json;
#[macro_use] extern crate glob;
#[macro_use] extern crate uuid;
extern crate config; extern crate config;
extern crate regex; extern crate regex;

11
src/module/command.rs Normal file
View file

@ -0,0 +1,11 @@
use std::result::Result;
use super::ModuleError;
use storage::backend::{StorageBackend, StorageBackendError};
type CommandError = Result<ModuleError, StorageBackendError>;
type CommandResult = Result<(), Result<ModuleError, CommandError>>;
pub trait ExecutableCommand {
fn exec(StorageBackend) -> CommandResult;
}

View file

@ -6,9 +6,9 @@ use std::fmt::Display;
use std::path::Path; use std::path::Path;
use std::result::Result; use std::result::Result;
use module::todo::TodoModule; use storage::backend::{StorageBackend, StorageBackendError};
use self::command::ExecutableCommand;
mod todo; mod command;
#[derive(Debug)] #[derive(Debug)]
pub struct ModuleError { pub struct ModuleError {
@ -52,5 +52,9 @@ pub trait Module {
fn execute(&self, rt : &Runtime) -> ModuleResult; fn execute(&self, rt : &Runtime) -> ModuleResult;
fn shutdown(&self, rt : &Runtime) -> ModuleResult; fn shutdown(&self, rt : &Runtime) -> ModuleResult;
fn getCommandBuilder<T, F>() -> F
where F: FnOnce(StorageBackend) -> T,
T: ExecutableCommand;
} }

View file

@ -1,36 +0,0 @@
use runtime::Runtime;
use module::Module;
use module::ModuleResult;
use std::path::Path;
use std::result::Result;
pub struct TodoModule {
path: Option<String>,
}
const CALLNAMES : &'static [&'static str] = &[ "todo" ];
impl Module for TodoModule {
fn new(rt : &Runtime) -> TodoModule {
TodoModule {
path: None
}
}
fn callnames() -> &'static [&'static str] {
CALLNAMES
}
fn name(&self) -> &'static str{
"Todo"
}
fn execute(&self, rt : &Runtime) -> ModuleResult {
Ok(())
}
fn shutdown(&self, rt : &Runtime) -> ModuleResult {
Ok(())
}
}

View file

@ -72,4 +72,12 @@ impl<'a> Runtime<'a> {
self.config.is_debugging() || self.configuration.is_verbose() self.config.is_debugging() || self.configuration.is_verbose()
} }
pub fn get_rtp(&self) -> String {
if let Some(rtp) = self.config.get_rtp() {
rtp
} else {
self.configuration.get_rtp()
}
}
} }

187
src/storage/backend.rs Normal file
View file

@ -0,0 +1,187 @@
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::io::Read;
use std::io::Write;
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<(), StorageBackendError>;
pub struct StorageBackend {
basepath: String,
}
impl StorageBackend {
pub fn new(basepath: String) -> StorageBackend {
StorageBackend {
basepath: basepath,
}
}
fn build<M: Module>(rt: &Runtime, m: &M) -> StorageBackend {
let path = rt.get_rtp() + m.name() + "/store";
StorageBackend::new(path)
}
fn get_file_ids(&self) -> Option<Vec<FileID>> {
let list = glob(&self.basepath[..]);
if let Ok(globlist) = list {
let mut v = vec![];
for entry in globlist {
if let Ok(path) = entry {
v.push(from_pathbuf(&path));
} else {
// Entry is not a path
}
}
Some(v)
} else {
None
}
}
/*
* Write a file to disk.
*
* 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>) ->
Result<BackendOperationResult, ParserError>
where HP: FileHeaderParser<'a>
{
let written = p.write(f.contents());
if let Ok(string) = written {
let path = self.build_filepath(&f);
debug!("Writing file: {}", path);
Ok(Ok(()))
} else {
Err(written.err().unwrap())
}
}
/*
* 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<'a, HP>(&self, f: File, p: &Parser<HP>)
-> Result<BackendOperationResult, ParserError>
where HP: FileHeaderParser<'a>
{
let contents = p.write(f.contents());
if contents.is_err() {
return Err(contents.err().unwrap());
}
let content = contents.unwrap();
let path = self.build_filepath(&f);
if let Err(_) = FSFile::open(&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) {
if let Err(writeerr) = file.write_all(&content.clone().into_bytes()) {
return Ok(Err(StorageBackendError::new(
String::from("File::write()"),
format!("Tried to write '{}'", path),
String::from("Tried to write contents of this file, though operation did not succeed"),
Some(content))))
}
}
Ok(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<'a, HP>(&self, id: FileID, p: &Parser<HP>) -> Option<File>
where HP: FileHeaderParser<'a>
{
if let Ok(mut fs) = FSFile::open(self.build_filepath_with_id(id.clone())) {
let mut s = String::new();
fs.read_to_string(&mut s);
p.read(s).and_then(|(h, d)| Ok(File::from_parser_result(id, h, d))).ok()
} else {
None
}
}
fn build_filepath(&self, f: &File) -> String {
self.build_filepath_with_id(f.id())
}
fn build_filepath_with_id(&self, id: FileID) -> String {
self.basepath.clone() + &id[..]
}
}
#[derive(Debug)]
pub struct StorageBackendError {
pub action: String, // The file system action in words
pub desc: String, // A short description
pub explanation: String, // A long, user friendly description
pub dataDump: Option<String> // Data dump, if any
}
impl StorageBackendError {
fn new(action: String,
desc : String,
explan: String,
data : Option<String>) -> StorageBackendError
{
StorageBackendError {
action: action,
desc: desc,
explanation: explan,
dataDump: data,
}
}
}
impl Error for StorageBackendError {
fn description(&self) -> &str {
&self.desc[..]
}
fn cause(&self) -> Option<&Error> {
None
}
}
impl Display for StorageBackendError {
fn fmt(&self, f: &mut Formatter) -> FMTResult {
write!(f, "StorageBackendError[{}]: {}\n\n{}",
self.action, self.desc, self.explanation)
}
}

View file

@ -3,6 +3,9 @@ use std::fmt::{Debug, Display, Formatter};
use std::fmt; use std::fmt;
use super::parser::{FileHeaderParser, Parser, ParserError}; use super::parser::{FileHeaderParser, Parser, ParserError};
use storage::file_id::*;
use std::fs::File as FSFile;
#[derive(Debug)] #[derive(Debug)]
pub enum FileHeaderSpec { pub enum FileHeaderSpec {
@ -18,6 +21,7 @@ pub enum FileHeaderSpec {
} }
#[derive(Debug)] #[derive(Debug)]
#[derive(Clone)]
pub enum FileHeaderData { pub enum FileHeaderData {
Null, Null,
Bool(bool), Bool(bool),
@ -162,39 +166,69 @@ pub fn match_header_spec<'a>(spec: &'a FileHeaderSpec, data: &'a FileHeaderData)
None None
} }
pub type FileID = String; /*
* Internal abstract view on a file. Does not exist on the FS and is just kept
* internally until it is written to disk.
*/
pub struct File { pub struct File {
header : FileHeaderData, header : FileHeaderData,
data : String, data : String,
id : String id : FileID,
} }
impl<'a> File { impl File {
fn new<HP>(prs: &Parser<HP>, path: &String) -> Result<File, ParserError> pub fn new() -> File {
where HP: FileHeaderParser<'a> File {
{ header: FileHeaderData::Null,
File::read_file(path).and_then(|p| prs.read(p)) data: String::from(""),
.and_then(|(h, d)| id: File::get_new_file_id(),
Ok(File { }
header: h,
data: d,
id: File::get_id_from_path(path),
}))
} }
fn getID(&self) -> FileID { pub fn from_parser_result(id: FileID, header: FileHeaderData, data: String) -> File {
File {
header: header,
data: data,
id: id,
}
}
pub fn new_with_header(h: FileHeaderData) -> File {
File {
header: h,
data: String::from(""),
id: File::get_new_file_id(),
}
}
pub fn new_with_data(d: String) -> File {
File {
header: FileHeaderData::Null,
data: d,
id: File::get_new_file_id(),
}
}
pub fn new_with_content(h: FileHeaderData, d: String) -> File {
File {
header: h,
data: d,
id: File::get_new_file_id(),
}
}
pub fn contents(&self) -> (FileHeaderData, String) {
(self.header.clone(), self.data.clone())
}
pub fn id(&self) -> FileID {
self.id.clone() self.id.clone()
} }
fn get_id_from_path(p: &String) -> FileID { fn get_new_file_id() -> FileID {
String::from("") use uuid::Uuid;
Uuid::new_v4().to_hyphenated_string()
} }
fn read_file(p: &String) -> Result<String, ParserError> {
Ok(String::from(""))
}
} }

16
src/storage/file_id.rs Normal file
View file

@ -0,0 +1,16 @@
use std::path::{Path, PathBuf};
pub type FileID = String;
pub fn from_path_string(s: &String) -> FileID {
String::from("")
}
pub fn from_path(p: &Path) -> FileID {
String::from("")
}
pub fn from_pathbuf(p: &PathBuf) -> FileID {
from_path(p.as_path())
}

View file

@ -5,7 +5,9 @@ pub use std::error::Error;
pub use runtime::Runtime; pub use runtime::Runtime;
pub mod file; pub mod file;
pub mod file_id;
pub mod parser; pub mod parser;
pub mod backend;
pub mod json; pub mod json;

View file

@ -112,7 +112,7 @@ impl<'a, HP> Parser<HP> where
Ok((h_parseres, data.unwrap_or(String::new()))) Ok((h_parseres, data.unwrap_or(String::new())))
} }
fn write(&self, tpl : (FileHeaderData, String)) -> Result<String, ParserError> pub fn write(&self, tpl : (FileHeaderData, String)) -> Result<String, ParserError>
{ {
let (header, data) = tpl; let (header, data) = tpl;
let h_text = try!(self.headerp.write(&header)); let h_text = try!(self.headerp.write(&header));