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:
commit
fe0ef09417
14 changed files with 305 additions and 62 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -5,6 +5,7 @@ dependencies = [
|
|||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
|
@ -71,6 +72,11 @@ dependencies = [
|
|||
"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]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.1.4"
|
||||
|
|
|
@ -12,6 +12,7 @@ log = "0.3.2"
|
|||
regex = "0.1.41"
|
||||
url = "0.2.37"
|
||||
uuid = "0.1.18"
|
||||
glob = "0.2.10"
|
||||
|
||||
config = "0.1.2"
|
||||
|
||||
|
|
|
@ -33,5 +33,9 @@ impl<'a> CliConfig<'a> {
|
|||
pub fn is_debugging(&self) -> bool {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ impl Configuration {
|
|||
format!("{}{}", self.rtp, self.store_sub)
|
||||
}
|
||||
|
||||
pub fn get_rtp(&self) -> String {
|
||||
self.rtp.clone()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn rtp_path(config: &CliConfig) -> String {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate serde;
|
||||
#[macro_use] extern crate serde_json;
|
||||
#[macro_use] extern crate glob;
|
||||
#[macro_use] extern crate uuid;
|
||||
extern crate config;
|
||||
extern crate regex;
|
||||
|
||||
|
|
11
src/module/command.rs
Normal file
11
src/module/command.rs
Normal 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;
|
||||
}
|
|
@ -6,9 +6,9 @@ use std::fmt::Display;
|
|||
use std::path::Path;
|
||||
use std::result::Result;
|
||||
|
||||
use module::todo::TodoModule;
|
||||
|
||||
mod todo;
|
||||
use storage::backend::{StorageBackend, StorageBackendError};
|
||||
use self::command::ExecutableCommand;
|
||||
mod command;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleError {
|
||||
|
@ -52,5 +52,9 @@ pub trait Module {
|
|||
fn execute(&self, rt : &Runtime) -> ModuleResult;
|
||||
fn shutdown(&self, rt : &Runtime) -> ModuleResult;
|
||||
|
||||
fn getCommandBuilder<T, F>() -> F
|
||||
where F: FnOnce(StorageBackend) -> T,
|
||||
T: ExecutableCommand;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -72,4 +72,12 @@ impl<'a> Runtime<'a> {
|
|||
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
187
src/storage/backend.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@ use std::fmt::{Debug, Display, Formatter};
|
|||
use std::fmt;
|
||||
|
||||
use super::parser::{FileHeaderParser, Parser, ParserError};
|
||||
use storage::file_id::*;
|
||||
|
||||
use std::fs::File as FSFile;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FileHeaderSpec {
|
||||
|
@ -18,6 +21,7 @@ pub enum FileHeaderSpec {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
pub enum FileHeaderData {
|
||||
Null,
|
||||
Bool(bool),
|
||||
|
@ -162,39 +166,69 @@ pub fn match_header_spec<'a>(spec: &'a FileHeaderSpec, data: &'a FileHeaderData)
|
|||
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 {
|
||||
header : FileHeaderData,
|
||||
data : String,
|
||||
id : String
|
||||
id : FileID,
|
||||
}
|
||||
|
||||
impl<'a> File {
|
||||
impl File {
|
||||
|
||||
fn new<HP>(prs: &Parser<HP>, path: &String) -> Result<File, ParserError>
|
||||
where HP: FileHeaderParser<'a>
|
||||
{
|
||||
File::read_file(path).and_then(|p| prs.read(p))
|
||||
.and_then(|(h, d)|
|
||||
Ok(File {
|
||||
header: h,
|
||||
data: d,
|
||||
id: File::get_id_from_path(path),
|
||||
}))
|
||||
pub fn new() -> File {
|
||||
File {
|
||||
header: FileHeaderData::Null,
|
||||
data: String::from(""),
|
||||
id: File::get_new_file_id(),
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn get_id_from_path(p: &String) -> FileID {
|
||||
String::from("")
|
||||
fn get_new_file_id() -> FileID {
|
||||
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
16
src/storage/file_id.rs
Normal 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())
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ pub use std::error::Error;
|
|||
pub use runtime::Runtime;
|
||||
|
||||
pub mod file;
|
||||
pub mod file_id;
|
||||
pub mod parser;
|
||||
pub mod backend;
|
||||
|
||||
pub mod json;
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ impl<'a, HP> Parser<HP> where
|
|||
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 h_text = try!(self.headerp.write(&header));
|
||||
|
|
Loading…
Reference in a new issue