Merge branch 'rewrite'
This commit is contained in:
commit
8ba7e34e34
17 changed files with 895 additions and 895 deletions
14
src/error.rs
14
src/error.rs
|
@ -1,14 +0,0 @@
|
|||
use std::error::Error;
|
||||
|
||||
struct ApplicationError {
|
||||
str: String
|
||||
}
|
||||
|
||||
impl Error for ApplicationError {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
}
|
||||
}
|
37
src/main.rs
37
src/main.rs
|
@ -16,10 +16,6 @@ use configuration::Configuration;
|
|||
use runtime::{ImagLogger, Runtime};
|
||||
use clap::App;
|
||||
use module::Module;
|
||||
use module::ModuleError;
|
||||
use module::CommandEnv;
|
||||
use module::bm::BMModule;
|
||||
use storage::Storage;
|
||||
|
||||
mod cli;
|
||||
mod configuration;
|
||||
|
@ -28,6 +24,8 @@ mod module;
|
|||
mod storage;
|
||||
mod ui;
|
||||
|
||||
use module::bm::BM;
|
||||
|
||||
fn main() {
|
||||
let yaml = load_yaml!("../etc/cli.yml");
|
||||
let app = App::from_yaml(yaml);
|
||||
|
@ -44,39 +42,12 @@ fn main() {
|
|||
|
||||
debug!("Runtime : {:?}", &rt);
|
||||
|
||||
let backend = Storage::new(&rt).unwrap_or_else(|e| {
|
||||
error!("Error: {}", e);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
if let Some(matches) = rt.config.cli_matches.subcommand_matches("bm") {
|
||||
let module = BMModule::new(&rt);
|
||||
let commands = module.get_commands(&rt);
|
||||
if let Some(command) = matches.subcommand_name() {
|
||||
debug!("Subcommand: {}", command);
|
||||
|
||||
let cmdenv = CommandEnv {
|
||||
rt: &rt,
|
||||
bk: &backend,
|
||||
matches: matches.subcommand_matches(command).unwrap(),
|
||||
};
|
||||
|
||||
let result = match commands.get(command) {
|
||||
Some(f) => f(&module, cmdenv),
|
||||
None => Err(ModuleError::new("No subcommand found")),
|
||||
};
|
||||
|
||||
debug!("Result of command: {:?}", result);
|
||||
let res = BM::new(&rt).exec(matches);
|
||||
info!("BM exited with {}", res);
|
||||
} else {
|
||||
debug!("No subcommand");
|
||||
}
|
||||
|
||||
module.shutdown(&rt);
|
||||
} else {
|
||||
// Err(ModuleError::mk("No commandline call"))
|
||||
info!("No commandline call...")
|
||||
}
|
||||
|
||||
|
||||
info!("Hello, world!");
|
||||
}
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
use std::vec::IntoIter;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use regex::Regex;
|
||||
|
||||
use module::{CommandEnv, CommandResult, Module, ModuleError};
|
||||
use module::bm::header::{build_header, get_tags_from_header};
|
||||
use runtime::Runtime;
|
||||
use storage::StorageError;
|
||||
use storage::file::File;
|
||||
use storage::json::parser::JsonHeaderParser;
|
||||
use storage::parser::Parser;
|
||||
use ui::file::{FilePrinter, TablePrinter};
|
||||
|
||||
pub fn add_command(module: &Module, env: CommandEnv) -> CommandResult {
|
||||
use url::Url;
|
||||
use module::helpers::utils::cli::get_tags;
|
||||
|
||||
let url = env.matches.value_of("url").unwrap();
|
||||
|
||||
if let Err(e) = Url::parse(url) {
|
||||
info!("Not an URL: '{}'", url);
|
||||
info!(" this will turn into an hard error before 0.1.0");
|
||||
debug!("URL parsing error: {:?}", e);
|
||||
}
|
||||
|
||||
let tags = get_tags(env.rt, env.matches);
|
||||
info!("Adding url '{}' with tags '{:?}'", url, tags);
|
||||
|
||||
let header = build_header(&String::from(url), &tags);
|
||||
let file = File::new_with_header(module, header);
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
let putres = env.bk.put_file(file, &parser);
|
||||
|
||||
putres.map_err(|sberr| {
|
||||
let mut err = ModuleError::new("Storage Backend Error");
|
||||
err.caused_by = Some(Box::new(sberr));
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_command(module: &Module, env: CommandEnv) -> CommandResult {
|
||||
let printer = TablePrinter::new(env.rt.is_verbose(), env.rt.is_debugging());
|
||||
let files = get_filtered_files_from_backend(module, &env);
|
||||
|
||||
debug!("Printing files now");
|
||||
files.map(|f| printer.print_files(f));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_command(module: &Module, env: CommandEnv) -> CommandResult {
|
||||
fn remove_by_id(module: &Module, env: CommandEnv, id: String, checked: bool) -> CommandResult {
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
let file = env.bk
|
||||
.get_file_by_id(module, &id.into(), &parser)
|
||||
.unwrap_or({
|
||||
info!("No files found");
|
||||
return Ok(())
|
||||
});
|
||||
|
||||
debug!("Remove file: {:?}", file);
|
||||
|
||||
if let Err(e) = env.bk.remove_file(module, file, checked) {
|
||||
debug!("Remove failed");
|
||||
let mut err = ModuleError::new("Removing file failed");
|
||||
err.caused_by = Some(Box::new(e));
|
||||
Err(err)
|
||||
} else {
|
||||
info!("Remove worked");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_by_filtering(module: &Module, env: CommandEnv, checked: bool) -> CommandResult {
|
||||
get_filtered_files_from_backend(module, &env).and_then(|files| {
|
||||
let nfiles = files.len();
|
||||
info!("Removing {} Files", nfiles);
|
||||
|
||||
let errs = files.map(|file| {
|
||||
debug!("Remove file: {:?}", file);
|
||||
env.bk.remove_file(module, file, checked)
|
||||
})
|
||||
.filter(|e| e.is_err())
|
||||
.map(|e| {
|
||||
let err = e.err().unwrap();
|
||||
warn!("Error occured in Filesystem operation: {}", err);
|
||||
err
|
||||
})
|
||||
.collect::<Vec<StorageError>>();
|
||||
|
||||
if errs.len() != 0 {
|
||||
warn!("{} Errors occured while removing {} files", errs.len(), nfiles);
|
||||
let moderr = ModuleError::new("File removal failed");
|
||||
|
||||
// TODO : Collect StorageErrors
|
||||
|
||||
Err(moderr)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let checked : bool = run_removal_checking(&env);
|
||||
debug!("Checked mode: {}", checked);
|
||||
|
||||
if let Some(id) = get_id(env.rt, env.matches) {
|
||||
debug!("Remove by id: {}", id);
|
||||
remove_by_id(module, env, id, checked)
|
||||
} else {
|
||||
debug!("Remove more than one file");
|
||||
remove_by_filtering(module, env, checked)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Private helpers
|
||||
*
|
||||
*/
|
||||
|
||||
fn get_filtered_files_from_backend<'a>(module: &'a Module,
|
||||
env: &CommandEnv)
|
||||
-> Result<IntoIter<File<'a>>, ModuleError>
|
||||
{
|
||||
use module::helpers::utils::cli::get_tags;
|
||||
|
||||
fn check_tags(tags: &Vec<String>, file: &File) -> bool {
|
||||
if tags.len() != 0 {
|
||||
debug!("Checking tags of: {:?}", file.id());
|
||||
get_tags_from_header(&file.header())
|
||||
.iter()
|
||||
.any(|t| tags.contains(t))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
let tags = get_tags(env.rt, env.matches);
|
||||
debug!("Tags: {:?}", tags);
|
||||
env.bk
|
||||
.iter_files(module, &parser)
|
||||
.map(|files| {
|
||||
files.filter(|file| {
|
||||
debug!("Backend returns file: {:?}", file);
|
||||
check_tags(&tags, file)
|
||||
}).filter(|file| {
|
||||
debug!("Checking matches of: {:?}", file.id());
|
||||
get_matcher(env.rt, env.matches)
|
||||
.map(|r| file.matches_with(&r))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.collect::<Vec<File>>()
|
||||
.into_iter()
|
||||
}).map_err(|e| {
|
||||
debug!("Error from Backend: {:?}", e);
|
||||
let mut merr = ModuleError::new("Could not filter files");
|
||||
merr.caused_by = Some(Box::new(e));
|
||||
merr
|
||||
})
|
||||
}
|
||||
|
||||
fn get_matcher<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Option<Regex> {
|
||||
debug!("Fetching matcher from commandline");
|
||||
if let Some(s) = sub.value_of("match") {
|
||||
if let Ok(r) = Regex::new(s) {
|
||||
return Some(r)
|
||||
} else {
|
||||
error!("Regex error, continuing without regex");
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
}
|
||||
|
||||
fn get_id<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Option<String> {
|
||||
debug!("Fetching id from commandline");
|
||||
sub.value_of("id").and_then(|s| Some(String::from(s)))
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether the commandline call was set to run the removal "checked",
|
||||
* so if another entry from the store refers to this ID, do not remove the file.
|
||||
*/
|
||||
fn run_removal_checking(env: &CommandEnv) -> bool {
|
||||
env.matches.is_present("check")
|
||||
}
|
|
@ -4,12 +4,14 @@ use storage::file::header::spec::FileHeaderSpec as FHS;
|
|||
|
||||
pub fn get_spec() -> FHS {
|
||||
FHS::Map {
|
||||
keys: vec![ headerhelpers::tags::spec::url_key(),
|
||||
headerhelpers::tags::spec::tags_key() ]
|
||||
keys: vec![
|
||||
headerhelpers::tags::spec::url_key(),
|
||||
headerhelpers::tags::spec::tags_key(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_header(url: &String, tags: &Vec<String>) -> FHD {
|
||||
pub fn build_header(url: String, tags: Vec<String>) -> FHD {
|
||||
FHD::Map {
|
||||
keys: vec![
|
||||
FHD::Key {
|
||||
|
@ -28,3 +30,7 @@ pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
|
|||
headerhelpers::tags::data::get_tags_from_header(header)
|
||||
}
|
||||
|
||||
pub fn get_url_from_header(header: &FHD) -> Option<String> {
|
||||
headerhelpers::data::get_url_from_header(header)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +1,223 @@
|
|||
mod header;
|
||||
mod commands;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt;
|
||||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::Result as FMTResult;
|
||||
use clap::ArgMatches;
|
||||
|
||||
use module::{CommandMap, Module, ModuleResult};
|
||||
use runtime::Runtime;
|
||||
use self::commands::*;
|
||||
use module::Module;
|
||||
|
||||
pub struct BMModule {
|
||||
path: Option<String>,
|
||||
use storage::file::hash::FileHash;
|
||||
use storage::file::id::FileID;
|
||||
use storage::parser::FileHeaderParser;
|
||||
use storage::parser::Parser;
|
||||
use storage::json::parser::JsonHeaderParser;
|
||||
|
||||
mod header;
|
||||
|
||||
pub struct BM<'a> {
|
||||
rt: &'a Runtime<'a>,
|
||||
}
|
||||
|
||||
const CALLNAMES : &'static [&'static str] = &[ "bm", "bookmark" ];
|
||||
impl<'a> BM<'a> {
|
||||
|
||||
impl BMModule {
|
||||
|
||||
pub fn new(rt : &Runtime) -> BMModule {
|
||||
BMModule {
|
||||
path: None
|
||||
pub fn new(rt: &'a Runtime<'a>) -> BM<'a> {
|
||||
BM {
|
||||
rt: rt,
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime(&self) -> &Runtime {
|
||||
&self.rt
|
||||
}
|
||||
|
||||
fn command_add(&self, matches: &ArgMatches) -> bool {
|
||||
use self::header::build_header;
|
||||
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
|
||||
let url = matches.value_of("url").map(String::from).unwrap(); // clap ensures this is present
|
||||
let tags = matches.value_of("tags").and_then(|s| {
|
||||
Some(s.split(",").map(String::from).collect())
|
||||
}).unwrap_or(vec![]);
|
||||
|
||||
debug!("Building header with");
|
||||
debug!(" url = '{:?}'", url);
|
||||
debug!(" tags = '{:?}'", tags);
|
||||
let header = build_header(url, tags);
|
||||
|
||||
let fileid = self.rt.store().new_file_with_header(self, header);
|
||||
self.rt.store().load(&fileid).and_then(|file| {
|
||||
info!("Created file in memory: {}", fileid);
|
||||
Some(self.rt.store().persist(&parser, file))
|
||||
}).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn command_list(&self, matches: &ArgMatches) -> bool {
|
||||
use ui::file::{FilePrinter, TablePrinter};
|
||||
use self::header::get_url_from_header;
|
||||
use self::header::get_tags_from_header;
|
||||
use std::ops::Deref;
|
||||
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
let files = self.rt.store().load_for_module(self, &parser);
|
||||
let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());
|
||||
|
||||
printer.print_files_custom(files.into_iter(),
|
||||
&|file| {
|
||||
let fl = file.deref().borrow();
|
||||
let hdr = fl.header();
|
||||
let url = get_url_from_header(hdr).unwrap_or(String::from("Parser error"));
|
||||
let tags = get_tags_from_header(hdr);
|
||||
|
||||
debug!("Custom printer field: url = '{:?}'", url);
|
||||
debug!("Custom printer field: tags = '{:?}'", tags);
|
||||
|
||||
vec![url, tags.join(", ")]
|
||||
}
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
fn command_remove(&self, matches: &ArgMatches) -> bool {
|
||||
use std::process::exit;
|
||||
|
||||
let result =
|
||||
if matches.is_present("id") {
|
||||
debug!("Removing by ID (Hash)");
|
||||
let hash = FileHash::from(matches.value_of("id").unwrap());
|
||||
self.remove_by_hash(hash)
|
||||
} else if matches.is_present("tags") {
|
||||
debug!("Removing by tags");
|
||||
let tags = matches.value_of("tags")
|
||||
.unwrap()
|
||||
.split(",")
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>();
|
||||
self.remove_by_tags(tags)
|
||||
} else if matches.is_present("match") {
|
||||
debug!("Removing by match");
|
||||
self.remove_by_match(String::from(matches.value_of("match").unwrap()))
|
||||
} else {
|
||||
error!("Unexpected error. Exiting");
|
||||
exit(1);
|
||||
false
|
||||
};
|
||||
|
||||
if result {
|
||||
info!("Removing succeeded");
|
||||
} else {
|
||||
info!("Removing failed");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn remove_by_hash(&self, hash: FileHash) -> bool {
|
||||
use std::ops::Deref;
|
||||
|
||||
debug!("Removing for hash = '{:?}'", hash);
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
|
||||
let file = self.rt.store().load_by_hash(self, &parser, hash);
|
||||
debug!("file = {:?}", file);
|
||||
file.map(|file| {
|
||||
debug!("File loaded, can remove now: {:?}", file);
|
||||
let f = file.deref().borrow();
|
||||
self.rt.store().remove(f.id().clone())
|
||||
}).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn remove_by_tags(&self, tags: Vec<String>) -> bool {
|
||||
use std::fs::remove_file;
|
||||
use std::ops::Deref;
|
||||
use self::header::get_tags_from_header;
|
||||
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
self.rt
|
||||
.store()
|
||||
.load_for_module(self, &parser)
|
||||
.iter()
|
||||
.filter(|file| {
|
||||
let f = file.deref().borrow();
|
||||
get_tags_from_header(f.header()).iter().any(|tag| {
|
||||
tags.iter().any(|remtag| remtag == tag)
|
||||
})
|
||||
}).map(|file| {
|
||||
let f = file.deref().borrow();
|
||||
self.rt.store().remove(f.id().clone())
|
||||
}).all(|x| x)
|
||||
}
|
||||
|
||||
fn remove_by_match(&self, matcher: String) -> bool {
|
||||
use self::header::get_url_from_header;
|
||||
use std::fs::remove_file;
|
||||
use std::ops::Deref;
|
||||
use std::process::exit;
|
||||
use regex::Regex;
|
||||
|
||||
let re = Regex::new(&matcher[..]).unwrap_or_else(|e| {
|
||||
error!("Cannot build regex out of '{}'", matcher);
|
||||
error!("{}", e);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
debug!("Compiled '{}' to regex: '{:?}'", matcher, re);
|
||||
|
||||
let parser = Parser::new(JsonHeaderParser::new(None));
|
||||
self.rt
|
||||
.store()
|
||||
.load_for_module(self, &parser)
|
||||
.iter()
|
||||
.filter(|file| {
|
||||
let f = file.deref().borrow();
|
||||
let url = get_url_from_header(f.header());
|
||||
debug!("url = {:?}", url);
|
||||
url.map(|u| {
|
||||
debug!("Matching '{}' ~= '{}'", re.as_str(), u);
|
||||
re.is_match(&u[..])
|
||||
}).unwrap_or(false)
|
||||
}).map(|file| {
|
||||
let f = file.deref().borrow();
|
||||
self.rt.store().remove(f.id().clone())
|
||||
}).all(|x| x)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Module for BMModule {
|
||||
impl<'a> Module<'a> for BM<'a> {
|
||||
|
||||
fn callnames(&self) -> &'static [&'static str] {
|
||||
CALLNAMES
|
||||
fn exec(&self, matches: &ArgMatches) -> bool {
|
||||
match matches.subcommand_name() {
|
||||
Some("add") => {
|
||||
self.command_add(matches.subcommand_matches("add").unwrap())
|
||||
},
|
||||
|
||||
Some("list") => {
|
||||
self.command_list(matches.subcommand_matches("list").unwrap())
|
||||
},
|
||||
|
||||
Some("remove") => {
|
||||
self.command_remove(matches.subcommand_matches("remove").unwrap())
|
||||
},
|
||||
|
||||
Some(_) | None => {
|
||||
info!("No command given, doing nothing");
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str{
|
||||
fn name(&self) -> &'static str {
|
||||
"bookmark"
|
||||
}
|
||||
|
||||
fn shutdown(&self, rt : &Runtime) -> ModuleResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_commands(&self, rt: &Runtime) -> CommandMap {
|
||||
let mut hm = CommandMap::new();
|
||||
hm.insert("add", add_command);
|
||||
hm.insert("list", list_command);
|
||||
hm.insert("remove", remove_command);
|
||||
hm
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BMModule {
|
||||
impl<'a> Debug for BM<'a> {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> FMTResult {
|
||||
write!(fmt, "[Module][BM]");
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "BM");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,43 @@
|
|||
|
||||
pub mod tags;
|
||||
|
||||
pub mod data {
|
||||
use std::ops::Deref;
|
||||
use storage::file::header::data::FileHeaderData as FHD;
|
||||
|
||||
pub fn get_url_from_header(header: &FHD) -> Option<String> {
|
||||
match header {
|
||||
&FHD::Map{keys: ref ks} => {
|
||||
let mut keys : Vec<FHD> = ks.clone();
|
||||
keys.iter().find(|k| {
|
||||
match k.deref() {
|
||||
&FHD::Key{name: ref n, value: ref v} => n == "URL",
|
||||
_ => false
|
||||
}
|
||||
}).and_then(|urlkey| {
|
||||
match urlkey.deref().clone() {
|
||||
FHD::Key{name: _, value: ref v} => {
|
||||
match v.deref().clone() {
|
||||
FHD::Text(s) => Some(s),
|
||||
_ => {
|
||||
warn!("Malformed Header Data: Expected Text, found non-Text");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("Malformed Header Data: Expected Text, found non-Text");
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
warn!("Malformed Header Data: Expected Map, found non-Map");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ pub mod data {
|
|||
use std::ops::Deref;
|
||||
use storage::file::header::data::FileHeaderData as FHD;
|
||||
|
||||
pub fn build_tag_array(tags: &Vec<String>) -> FHD {
|
||||
pub fn build_tag_array(tags: Vec<String>) -> FHD {
|
||||
let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect();
|
||||
FHD::Array { values: Box::new(texttags) }
|
||||
}
|
||||
|
@ -63,6 +63,5 @@ pub mod data {
|
|||
tags
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,61 +7,12 @@ use std::result::Result;
|
|||
use clap::ArgMatches;
|
||||
|
||||
use runtime::Runtime;
|
||||
use storage::Storage;
|
||||
|
||||
pub mod bm;
|
||||
pub mod helpers;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleError {
|
||||
desc: String,
|
||||
caused_by: Option<Box<Error>>,
|
||||
}
|
||||
|
||||
impl ModuleError {
|
||||
pub fn new(desc: &'static str) -> ModuleError {
|
||||
ModuleError {
|
||||
desc: desc.to_owned().to_string(),
|
||||
caused_by: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ModuleError {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
&self.desc[..]
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
self.caused_by.as_ref().map(|e| &**e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Display for ModuleError {
|
||||
fn fmt(&self, f: &mut Formatter) -> FMTResult {
|
||||
write!(f, "ModuleError: {}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandEnv<'a> {
|
||||
pub rt: &'a Runtime<'a>,
|
||||
pub bk: &'a Storage,
|
||||
pub matches: &'a ArgMatches<'a, 'a>,
|
||||
}
|
||||
|
||||
pub type ModuleResult = Result<(), ModuleError>;
|
||||
pub type CommandResult = ModuleResult;
|
||||
pub type CommandMap<'a> = HashMap<&'a str, fn(&Module, CommandEnv) -> CommandResult>;
|
||||
|
||||
pub trait Module : Debug {
|
||||
|
||||
fn callnames(&self) -> &'static [&'static str];
|
||||
pub trait Module<'a> : Debug {
|
||||
fn exec(&self, matches: &ArgMatches) -> bool;
|
||||
fn name(&self) -> &'static str;
|
||||
fn shutdown(&self, rt : &Runtime) -> ModuleResult;
|
||||
|
||||
fn get_commands(&self, rt: &Runtime) -> CommandMap;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError};
|
|||
pub use cli::CliConfig;
|
||||
pub use configuration::Configuration as Cfg;
|
||||
|
||||
use storage::Store;
|
||||
|
||||
pub struct ImagLogger {
|
||||
lvl: LogLevel,
|
||||
}
|
||||
|
@ -52,14 +54,17 @@ impl log::Log for ImagLogger {
|
|||
pub struct Runtime<'a> {
|
||||
pub config : CliConfig<'a>,
|
||||
pub configuration : Cfg,
|
||||
pub store : Store,
|
||||
}
|
||||
|
||||
impl<'a> Runtime<'a> {
|
||||
|
||||
pub fn new(cfg: Cfg, config : CliConfig<'a>) -> Runtime<'a> {
|
||||
let sp = config.store_path().unwrap_or(cfg.store_path());
|
||||
Runtime {
|
||||
config: config,
|
||||
configuration: cfg,
|
||||
store: Store::new(sp),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +80,10 @@ impl<'a> Runtime<'a> {
|
|||
self.config.store_path().unwrap_or(self.configuration.store_path())
|
||||
}
|
||||
|
||||
pub fn store(&self) -> &Store {
|
||||
&self.store
|
||||
}
|
||||
|
||||
pub fn get_rtp(&self) -> String {
|
||||
if let Some(rtp) = self.config.get_rtp() {
|
||||
rtp
|
||||
|
|
63
src/storage/file/hash.rs
Normal file
63
src/storage/file/hash.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use std::convert::{From, Into};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Eq)]
|
||||
#[derive(Hash)]
|
||||
pub struct FileHash {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
impl From<String> for FileHash {
|
||||
|
||||
fn from(s: String) -> FileHash {
|
||||
FileHash { hash: s }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for FileHash {
|
||||
|
||||
fn from(s: &'a String) -> FileHash {
|
||||
FileHash::from(s.clone())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl From<Uuid> for FileHash {
|
||||
|
||||
fn from(u: Uuid) -> FileHash {
|
||||
FileHash::from(u.to_hyphenated_string())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for FileHash {
|
||||
|
||||
fn from(s: &str) -> FileHash {
|
||||
FileHash::from(String::from(s))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Into<String> for FileHash {
|
||||
|
||||
fn into(self) -> String {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileHash {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.hash);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
|
@ -2,80 +2,81 @@ use std::convert::{From, Into};
|
|||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
use storage::file::id_type::FileIDType;
|
||||
use storage::file::hash::FileHash;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Hash)]
|
||||
#[derive(Eq)]
|
||||
// #[derive(Display)]
|
||||
pub enum FileIDType {
|
||||
NONE,
|
||||
UUID,
|
||||
}
|
||||
|
||||
impl Into<String> for FileIDType {
|
||||
|
||||
fn into(self) -> String {
|
||||
let s = match self {
|
||||
FileIDType::UUID => "UUID",
|
||||
FileIDType::NONE => "",
|
||||
};
|
||||
|
||||
String::from(s)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for FileIDType {
|
||||
|
||||
fn from(s: &'a str) -> FileIDType {
|
||||
match s {
|
||||
"UUID" => FileIDType::UUID,
|
||||
_ => FileIDType::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl From<String> for FileIDType {
|
||||
|
||||
fn from(s: String) -> FileIDType {
|
||||
FileIDType::from(&s[..])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(PartialEq)]
|
||||
pub struct FileID {
|
||||
id: Option<String>,
|
||||
id: FileHash,
|
||||
id_type: FileIDType,
|
||||
}
|
||||
|
||||
impl FileID {
|
||||
|
||||
pub fn new(id_type: FileIDType, id: String) -> FileID {
|
||||
pub fn new(id_type: FileIDType, id: FileHash) -> FileID {
|
||||
FileID {
|
||||
id: Some(id),
|
||||
id: id,
|
||||
id_type: id_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.id.is_some()
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> FileIDType {
|
||||
self.id_type.clone()
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Option<String> {
|
||||
pub fn get_id(&self) -> FileHash {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn parse(string: &String) -> Option<FileID> {
|
||||
// we assume that it is an path
|
||||
let regex = Regex::new(r"([:alnum:]*)-([:upper:]*)-([A-Za-z0-9-_]*)\.(.*)").unwrap();
|
||||
let s = string.split("/").last().unwrap_or("");
|
||||
|
||||
debug!("Regex build: {:?}", regex);
|
||||
debug!("Matching string: '{}'", s);
|
||||
regex.captures(s).and_then(|capts| {
|
||||
// first one is the whole string, index 1-N are the matches.
|
||||
if capts.len() != 5 {
|
||||
debug!("Matches, but not expected number of groups");
|
||||
return None;
|
||||
}
|
||||
debug!("Matches: {}", capts.len());
|
||||
|
||||
let modname = capts.at(1).unwrap();
|
||||
let hashname = capts.at(2).unwrap();
|
||||
let mut hash = capts.at(3).unwrap();
|
||||
|
||||
debug!("Destructure FilePath to ID:");
|
||||
debug!(" FilePath: {:?}", s);
|
||||
debug!(" Module Name: {:?}", modname);
|
||||
debug!(" Hash Name: {:?}", hashname);
|
||||
debug!(" Hash: {:?}", hash);
|
||||
|
||||
FileIDType::from_str(hashname).map(|idtype| {
|
||||
debug!("ID type = {:?}", idtype);
|
||||
Some(FileID {
|
||||
id: FileHash::from(hash),
|
||||
id_type: idtype,
|
||||
})
|
||||
}).ok()
|
||||
}).unwrap_or({
|
||||
debug!("Did not match");
|
||||
debug!("It is no path, actually. So we assume it is an ID already");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Debug for FileID {
|
||||
|
@ -103,141 +104,12 @@ impl Display for FileID {
|
|||
impl Into<String> for FileID {
|
||||
|
||||
fn into(self) -> String {
|
||||
if let Some(id) = self.id {
|
||||
id.clone()
|
||||
} else {
|
||||
String::from("INVALID")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Into<FileIDType> for FileID {
|
||||
|
||||
fn into(self) -> FileIDType {
|
||||
self.id_type.clone()
|
||||
let typestr : String = self.id_type.into();
|
||||
let idstr : String = self.id.into();
|
||||
typestr + "-" + &idstr[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for FileID {
|
||||
|
||||
fn from(s: String) -> FileID {
|
||||
FileID::from(&s)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for FileID {
|
||||
|
||||
fn from(string: &'a String) -> FileID {
|
||||
// we assume that it is an path
|
||||
let regex = Regex::new(r"([:alnum:]*)-([:upper:]*)-([A-Za-z0-9-_]*)\.(.*)").unwrap();
|
||||
let s = string.split("/").last().unwrap_or("");
|
||||
|
||||
debug!("Regex build: {:?}", regex);
|
||||
debug!("Matching string: '{}'", s);
|
||||
regex.captures(s).and_then(|capts| {
|
||||
// first one is the whole string, index 1-N are the matches.
|
||||
if capts.len() != 5 {
|
||||
debug!("Matches, but not expected number of groups");
|
||||
return None;
|
||||
}
|
||||
debug!("Matches: {}", capts.len());
|
||||
|
||||
let modname = capts.at(1).unwrap();
|
||||
let hashname = capts.at(2).unwrap();
|
||||
let mut hash = capts.at(3).unwrap();
|
||||
|
||||
debug!("Destructure FilePath to ID:");
|
||||
debug!(" FilePath: {:?}", s);
|
||||
debug!(" Module Name: {:?}", modname);
|
||||
debug!(" Hash Name: {:?}", hashname);
|
||||
debug!(" Hash: {:?}", hash);
|
||||
|
||||
let idtype = FileIDType::from(hashname);
|
||||
match idtype {
|
||||
FileIDType::NONE => hash = "INVALID",
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Some(FileID::new(idtype, String::from(hash)))
|
||||
}).unwrap_or({
|
||||
debug!("Did not match");
|
||||
debug!("It is no path, actually. So we assume it is an ID already");
|
||||
FileID {
|
||||
id_type: FileIDType::NONE,
|
||||
id: Some(string.clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl From<PathBuf> for FileID {
|
||||
|
||||
fn from(s: PathBuf) -> FileID {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a PathBuf> for FileID {
|
||||
|
||||
fn from(pb: &'a PathBuf) -> FileID {
|
||||
let s = pb.to_str().unwrap_or("");
|
||||
FileID::from(String::from(s))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct FileIDError {
|
||||
summary: String,
|
||||
descrip: String,
|
||||
}
|
||||
|
||||
impl FileIDError {
|
||||
|
||||
pub fn new(s: String, d: String) -> FileIDError {
|
||||
FileIDError {
|
||||
summary: s,
|
||||
descrip: d,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Error for FileIDError {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
&self.summary[..]
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
None
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Debug for FileIDError {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "FileIDError: '{}'\n{}", self.summary, self.descrip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Display for FileIDError {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "FileIDError: '{}'", self.summary);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub type FileIDResult = Result<FileID, FileIDError>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
|
38
src/storage/file/id_type.rs
Normal file
38
src/storage/file/id_type.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::convert::{From, Into};
|
||||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Eq)]
|
||||
// #[derive(Display)]
|
||||
#[derive(Hash)]
|
||||
pub enum FileIDType {
|
||||
UUID,
|
||||
}
|
||||
|
||||
pub enum FileIDTypeParseError {
|
||||
UnknownType
|
||||
}
|
||||
|
||||
impl FromStr for FileIDType {
|
||||
type Err = FileIDTypeParseError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"UUID" => Ok(FileIDType::UUID),
|
||||
_ => Err(FileIDTypeParseError::UnknownType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for FileIDType {
|
||||
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
FileIDType::UUID => String::from("UUID"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,15 @@ use std::fmt;
|
|||
use regex::Regex;
|
||||
|
||||
pub mod id;
|
||||
pub mod id_type;
|
||||
pub mod header;
|
||||
pub mod hash;
|
||||
|
||||
|
||||
use module::Module;
|
||||
use storage::file::id::*;
|
||||
use storage::file::id_type::FileIDType;
|
||||
use storage::file::hash::FileHash;
|
||||
use super::parser::{FileHeaderParser, Parser, ParserError};
|
||||
|
||||
use self::header::spec::*;
|
||||
|
@ -18,101 +23,42 @@ use self::header::data::*;
|
|||
* 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<'a> {
|
||||
owning_module : &'a Module,
|
||||
header : FileHeaderData,
|
||||
data : String,
|
||||
id : FileID,
|
||||
pub struct File {
|
||||
pub owning_module_name : &'static str,
|
||||
pub header : FileHeaderData,
|
||||
pub data : String,
|
||||
pub id : FileID,
|
||||
}
|
||||
|
||||
impl<'a> File<'a> {
|
||||
impl File {
|
||||
|
||||
pub fn new(module: &'a Module) -> File<'a> {
|
||||
let f = File {
|
||||
owning_module: module,
|
||||
header: FileHeaderData::Null,
|
||||
data: String::from(""),
|
||||
id: File::get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object: {:?}", f);
|
||||
f
|
||||
pub fn owner_name(&self) -> &'static str {
|
||||
self.owning_module_name
|
||||
}
|
||||
|
||||
pub fn from_parser_result(module: &Module, id: FileID, header: FileHeaderData, data: String) -> File {
|
||||
let f = File {
|
||||
owning_module: module,
|
||||
header: header,
|
||||
data: data,
|
||||
id: id,
|
||||
};
|
||||
debug!("Create new File object from parser result: {:?}", f);
|
||||
f
|
||||
pub fn header(&self) -> &FileHeaderData {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn new_with_header(module: &Module, h: FileHeaderData) -> File {
|
||||
let f = File {
|
||||
owning_module: module,
|
||||
header: h,
|
||||
data: String::from(""),
|
||||
id: File::get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with header: {:?}", f);
|
||||
f
|
||||
pub fn data(&self) -> &String {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn new_with_data(module: &Module, d: String) -> File {
|
||||
let f = File {
|
||||
owning_module: module,
|
||||
header: FileHeaderData::Null,
|
||||
data: d,
|
||||
id: File::get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with data: {:?}", f);
|
||||
f
|
||||
}
|
||||
|
||||
pub fn new_with_content(module: &Module, h: FileHeaderData, d: String) -> File {
|
||||
let f = File {
|
||||
owning_module: module,
|
||||
header: h,
|
||||
data: d,
|
||||
id: File::get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with content: {:?}", 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(), self.data())
|
||||
}
|
||||
|
||||
pub fn id(&self) -> FileID {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> &Module {
|
||||
self.owning_module
|
||||
pub fn id(&self) -> &FileID {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn matches_with(&self, r: &Regex) -> bool {
|
||||
r.is_match(&self.data[..]) || self.header.matches_with(r)
|
||||
}
|
||||
|
||||
fn get_new_file_id() -> FileID {
|
||||
use uuid::Uuid;
|
||||
FileID::new(FileIDType::UUID, Uuid::new_v4().to_hyphenated_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for File<'a> {
|
||||
impl Display for File {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt,
|
||||
|
@ -120,7 +66,7 @@ impl<'a> Display for File<'a> {
|
|||
FileID: '{:?}'
|
||||
Header: '{:?}'
|
||||
Data : '{:?}'",
|
||||
self.owning_module,
|
||||
self.owning_module_name,
|
||||
self.header,
|
||||
self.data,
|
||||
self.id);
|
||||
|
@ -129,7 +75,7 @@ impl<'a> Display for File<'a> {
|
|||
|
||||
}
|
||||
|
||||
impl<'a> Debug for File<'a> {
|
||||
impl Debug for File {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt,
|
||||
|
@ -137,10 +83,10 @@ impl<'a> Debug for File<'a> {
|
|||
FileID: '{:?}'
|
||||
Header: '{:?}'
|
||||
Data : '{:?}'",
|
||||
self.owning_module,
|
||||
self.owning_module_name,
|
||||
self.id,
|
||||
self.header,
|
||||
self.data,
|
||||
self.id);
|
||||
self.data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,324 +1,285 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Result as FMTResult;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File as FSFile;
|
||||
use std::fs::{create_dir_all, remove_file};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::vec::{Vec, IntoIter};
|
||||
use std::ops::Deref;
|
||||
use std::io::Write;
|
||||
use std::io::Read;
|
||||
|
||||
pub mod path;
|
||||
pub mod file;
|
||||
pub mod parser;
|
||||
pub mod json;
|
||||
|
||||
use glob::glob;
|
||||
use glob::Paths;
|
||||
|
||||
use module::Module;
|
||||
use runtime::Runtime;
|
||||
use storage::file::File;
|
||||
use storage::file::id::*;
|
||||
use storage::parser::{FileHeaderParser, Parser};
|
||||
use storage::file::id::FileID;
|
||||
use storage::file::id_type::FileIDType;
|
||||
use storage::file::hash::FileHash;
|
||||
use storage::parser::{FileHeaderParser, Parser, ParserError};
|
||||
use storage::file::header::data::FileHeaderData;
|
||||
|
||||
pub type BackendOperationResult<T = ()> = Result<T, StorageError>;
|
||||
type Cache = HashMap<FileID, Rc<RefCell<File>>>;
|
||||
|
||||
pub struct Storage {
|
||||
basepath: String,
|
||||
pub struct Store {
|
||||
storepath: String,
|
||||
cache : RefCell<Cache>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
impl Store {
|
||||
|
||||
pub fn new(rt: &Runtime) -> BackendOperationResult<Storage> {
|
||||
use self::StorageError as SBE;
|
||||
|
||||
let storepath = rt.get_rtp() + "/store/";
|
||||
debug!("Trying to create {}", storepath);
|
||||
create_dir_all(&storepath).and_then(|_| {
|
||||
debug!("Creating succeeded, constructing backend instance");
|
||||
Ok(Storage {
|
||||
basepath: rt.get_rtp(),
|
||||
storepath: storepath.clone(),
|
||||
})
|
||||
}).or_else(|e| {
|
||||
debug!("Creating failed, constructing error instance");
|
||||
Err(SBE::new("create_dir_all()", "Could not create store directories",
|
||||
Some(storepath), Some(Box::new(e))))
|
||||
})
|
||||
pub fn new(storepath: String) -> Store {
|
||||
Store {
|
||||
storepath: storepath,
|
||||
cache: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_ids(&self, m: &Module) -> Result<IntoIter<FileID>, StorageError>
|
||||
{
|
||||
use self::StorageError as SBE;
|
||||
|
||||
let globstr = self.prefix_of_files_for_module(m) + "*.imag";
|
||||
debug!("Globstring = {}", globstr);
|
||||
glob(&globstr[..])
|
||||
.and_then(|globlist| {
|
||||
debug!("Iterating over globlist");
|
||||
Ok(globlist_to_file_id_vec(globlist).into_iter())
|
||||
})
|
||||
.map_err(|e| {
|
||||
debug!("glob() returned error: {:?}", e);
|
||||
SBE::new("iter_ids()", "Cannot iter on file ids", None, None)
|
||||
})
|
||||
fn put_in_cache(&self, f: File) -> FileID {
|
||||
let res = f.id().clone();
|
||||
self.cache.borrow_mut().insert(f.id().clone(), Rc::new(RefCell::new(f)));
|
||||
res
|
||||
}
|
||||
|
||||
pub fn iter_files<'a, HP>(&self, m: &'a Module, p: &Parser<HP>)
|
||||
-> Result<IntoIter<File<'a>>, StorageError>
|
||||
pub fn load_in_cache<HP>(&self, m: &Module, parser: &Parser<HP>, id: FileID)
|
||||
-> Option<Rc<RefCell<File>>>
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
use self::StorageError as SBE;
|
||||
|
||||
self.iter_ids(m)
|
||||
.and_then(|ids| {
|
||||
debug!("Iterating ids and building files from them");
|
||||
debug!(" number of ids = {}", ids.len());
|
||||
Ok(self.filter_map_ids_to_files(m, p, ids).into_iter())
|
||||
})
|
||||
.map_err(|e| {
|
||||
debug!("Storage::iter_ids() returned error = {:?}", e);
|
||||
SBE::new("iter_files()", "Cannot iter on files", None,
|
||||
Some(Box::new(e)))
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a file to disk.
|
||||
*
|
||||
* The file is moved to this function as the file won't be edited afterwards
|
||||
*/
|
||||
pub fn put_file<HP>(&self, f: File, p: &Parser<HP>) -> BackendOperationResult
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
use self::StorageError as SBE;
|
||||
|
||||
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");
|
||||
SBE::new("File::write_all()",
|
||||
"Could not write out File contents",
|
||||
None, Some(Box::new(ioerr)))
|
||||
})
|
||||
}).map_err(|writeerr| {
|
||||
debug!("Could not create file at '{}'", path);
|
||||
SBE::new("File::create()", "Creating file on disk failed", None,
|
||||
Some(Box::new(writeerr)))
|
||||
}).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<HP>(&self, f: File, p: &Parser<HP>) -> BackendOperationResult
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
use self::StorageError as SBE;
|
||||
|
||||
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);
|
||||
let idstr : String = id.clone().into();
|
||||
let path = format!("{}/{}-{}.imag", self.storepath, m.name(), idstr);
|
||||
debug!("Loading path = '{}'", path);
|
||||
let mut string = String::new();
|
||||
|
||||
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");
|
||||
SBE::new("File::write()",
|
||||
"Tried to write contents of this file, though operation did not succeed",
|
||||
Some(string), Some(Box::new(ioerr)))
|
||||
})
|
||||
}).map_err(|writeerr| {
|
||||
debug!("Could not write file at '{}'", path);
|
||||
SBE::new("File::open()",
|
||||
"Tried to update contents of this file, though file doesn't exist",
|
||||
None, Some(Box::new(writeerr)))
|
||||
}).and(Ok(()))
|
||||
file.read_to_string(&mut string)
|
||||
.map_err(|e| error!("Failed reading file: '{}'", path));
|
||||
});
|
||||
|
||||
parser.read(string).map(|(header, data)| {
|
||||
self.new_file_from_parser_result(m, id.clone(), header, data);
|
||||
});
|
||||
|
||||
self.load(&id)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, m: &'a Module, id: &FileID, p: &Parser<HP>) -> Option<File<'a>>
|
||||
where HP: FileHeaderParser
|
||||
pub fn new_file(&self, module: &Module)
|
||||
-> FileID
|
||||
{
|
||||
debug!("Searching for file with id '{}'", id);
|
||||
let f = File {
|
||||
owning_module_name: module.name(),
|
||||
header: FileHeaderData::Null,
|
||||
data: String::from(""),
|
||||
id: self.get_new_file_id(),
|
||||
};
|
||||
|
||||
if id.get_type() == FileIDType::NONE {
|
||||
// We don't know the hash type, so we glob() around a bit.
|
||||
debug!("Having FileIDType::NONE, so we glob() for the raw ID");
|
||||
|
||||
let id_str = id.get_id().unwrap_or(String::from("INVALID"));
|
||||
let globstr = self.prefix_of_files_for_module(m) + "*" + &id_str[..] + ".imag";
|
||||
debug!("Globbing with globstr = '{}'", globstr);
|
||||
glob(&globstr[..]).map(|globlist| {
|
||||
let idvec = globlist_to_file_id_vec(globlist).into_iter();
|
||||
let mut vec = self.filter_map_ids_to_files(m, p, idvec);
|
||||
vec.reverse();
|
||||
vec.pop()
|
||||
}).unwrap_or({
|
||||
debug!("No glob matches, actually. We can't do anything at this point");
|
||||
None
|
||||
})
|
||||
} else {
|
||||
// The (hash)type is already in the FileID object, so we can just
|
||||
// build a path from the information we already have
|
||||
debug!("We know FileIDType, so we build the path directly now");
|
||||
let filepath = self.build_filepath_with_id(m, id.clone());
|
||||
if let Ok(mut fs) = FSFile::open(filepath) {
|
||||
let mut s = String::new();
|
||||
fs.read_to_string(&mut s);
|
||||
|
||||
debug!("Success opening 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
|
||||
}
|
||||
}
|
||||
debug!("Create new File object: {:?}", &f);
|
||||
self.put_in_cache(f)
|
||||
}
|
||||
|
||||
pub fn remove_file(&self, m: &Module, file: File, checked: bool) -> BackendOperationResult {
|
||||
use self::StorageError as SBE;
|
||||
|
||||
if checked {
|
||||
error!("Checked remove not implemented yet. I will crash now");
|
||||
unimplemented!()
|
||||
pub fn new_file_from_parser_result(&self,
|
||||
module: &Module,
|
||||
id: FileID,
|
||||
header: FileHeaderData,
|
||||
data: String)
|
||||
-> FileID
|
||||
{
|
||||
let f = File {
|
||||
owning_module_name: module.name(),
|
||||
header: header,
|
||||
data: data,
|
||||
id: id,
|
||||
};
|
||||
debug!("Create new File object from parser result: {:?}", f);
|
||||
self.put_in_cache(f)
|
||||
}
|
||||
|
||||
debug!("Doing unchecked remove");
|
||||
info!("Going to remove file: {}", file);
|
||||
|
||||
let fp = self.build_filepath(&file);
|
||||
remove_file(fp).map_err(|e| {
|
||||
SBE::new("remove_file()", "File removal failed",
|
||||
Some(format!("{}", file)), Some(Box::new(e)))
|
||||
})
|
||||
pub fn new_file_with_header(&self,
|
||||
module: &Module,
|
||||
h: FileHeaderData)
|
||||
-> FileID
|
||||
{
|
||||
let f = File {
|
||||
owning_module_name: module.name(),
|
||||
header: h,
|
||||
data: String::from(""),
|
||||
id: self.get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with header: {:?}", f);
|
||||
self.put_in_cache(f)
|
||||
}
|
||||
|
||||
fn build_filepath(&self, f: &File) -> String {
|
||||
self.build_filepath_with_id(f.owner(), f.id())
|
||||
pub fn new_file_with_data(&self, module: &Module, d: String)
|
||||
-> FileID
|
||||
{
|
||||
let f = File {
|
||||
owning_module_name: module.name(),
|
||||
header: FileHeaderData::Null,
|
||||
data: d,
|
||||
id: self.get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with data: {:?}", f);
|
||||
self.put_in_cache(f)
|
||||
}
|
||||
|
||||
fn build_filepath_with_id(&self, owner: &Module, id: FileID) -> String {
|
||||
let idstr : String = id.clone().into();
|
||||
let idtype : FileIDType = id.into();
|
||||
let typestr : String = idtype.into();
|
||||
|
||||
debug!("Building filepath with id");
|
||||
debug!(" basepath: '{}'", self.basepath);
|
||||
debug!(" storepath: '{}'", self.storepath);
|
||||
debug!(" id: '{}'", idstr);
|
||||
debug!(" type: '{}'", typestr);
|
||||
|
||||
self.prefix_of_files_for_module(owner) +
|
||||
"-" + &typestr[..] +
|
||||
"-" + &idstr[..] +
|
||||
".imag"
|
||||
pub fn new_file_with_content(&self,
|
||||
module: &Module,
|
||||
h: FileHeaderData,
|
||||
d: String)
|
||||
-> FileID
|
||||
{
|
||||
let f = File {
|
||||
owning_module_name: module.name(),
|
||||
header: h,
|
||||
data: d,
|
||||
id: self.get_new_file_id(),
|
||||
};
|
||||
debug!("Create new File object with content: {:?}", f);
|
||||
self.put_in_cache(f)
|
||||
}
|
||||
|
||||
fn prefix_of_files_for_module(&self, m: &Module) -> String {
|
||||
self.storepath.clone() + m.name()
|
||||
}
|
||||
|
||||
fn filter_map_ids_to_files<'a, HP>(&self,
|
||||
m: &'a Module,
|
||||
pub fn persist<HP>(&self,
|
||||
p: &Parser<HP>,
|
||||
ids: IntoIter<FileID>)
|
||||
-> Vec<File<'a>>
|
||||
f: Rc<RefCell<File>>) -> bool
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
ids.filter_map(|id| self.get_file_by_id(m, &id, p))
|
||||
.collect::<Vec<File>>()
|
||||
let file = f.deref().borrow();
|
||||
let text = p.write(file.contents());
|
||||
if text.is_err() {
|
||||
error!("Error: {}", text.err().unwrap());
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
let path = {
|
||||
let ids : String = file.id().clone().into();
|
||||
format!("{}/{}-{}.imag", self.storepath, file.owning_module_name, ids)
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StorageError {
|
||||
pub action: String, // The file system action in words
|
||||
pub desc: String, // A short description
|
||||
pub data_dump: Option<String>, // Data dump, if any
|
||||
pub caused_by: Option<Box<Error>>, // caused from this error
|
||||
}
|
||||
self.ensure_store_path_exists();
|
||||
|
||||
impl StorageError {
|
||||
|
||||
fn new<S>(action: S,
|
||||
desc: S,
|
||||
data: Option<String>,
|
||||
cause: Option<Box<Error>>)
|
||||
-> StorageError
|
||||
where S: Into<String>
|
||||
{
|
||||
StorageError {
|
||||
action: action.into(),
|
||||
desc: desc.into(),
|
||||
data_dump: data,
|
||||
caused_by: None,
|
||||
}
|
||||
FSFile::create(&path).map(|mut fsfile| {
|
||||
fsfile.write_all(&text.unwrap().clone().into_bytes()[..])
|
||||
}).map_err(|writeerr| {
|
||||
debug!("Could not create file at '{}'", path);
|
||||
}).and(Ok(true)).unwrap()
|
||||
}
|
||||
|
||||
}
|
||||
fn ensure_store_path_exists(&self) {
|
||||
use std::fs::create_dir_all;
|
||||
use std::process::exit;
|
||||
|
||||
impl Error for StorageError {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
&self.desc[..]
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
self.caused_by.as_ref().map(|e| &**e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Display for StorageError {
|
||||
fn fmt(&self, f: &mut Formatter) -> FMTResult {
|
||||
write!(f, "StorageError[{}]: {}",
|
||||
self.action, self.desc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn write_with_parser<'a, HP>(f: &File, p: &Parser<HP>) -> Result<String, StorageError>
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
use self::StorageError as SBE;
|
||||
|
||||
p.write(f.contents())
|
||||
.or_else(|err| {
|
||||
Err(SBE::new("Parser::write()",
|
||||
"Cannot translate internal representation of file contents into on-disk representation",
|
||||
None, Some(Box::new(err))))
|
||||
create_dir_all(&self.storepath).unwrap_or_else(|e| {
|
||||
error!("Could not create store: '{}'", self.storepath);
|
||||
error!("Error : '{}'", e);
|
||||
error!("Killing myself now");
|
||||
exit(1);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn globlist_to_file_id_vec(globlist: Paths) -> Vec<FileID> {
|
||||
globlist.filter_map(Result::ok)
|
||||
.map(|pbuf| FileID::from(&pbuf))
|
||||
.collect::<Vec<FileID>>()
|
||||
}
|
||||
pub fn load(&self, id: &FileID) -> Option<Rc<RefCell<File>>> {
|
||||
debug!("Loading '{:?}'", id);
|
||||
self.cache.borrow().get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn load_by_hash<HP>(&self,
|
||||
m: &Module,
|
||||
parser: &Parser<HP>,
|
||||
hash: FileHash)
|
||||
-> Option<Rc<RefCell<File>>>
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
macro_rules! try_some {
|
||||
($expr:expr) => (match $expr {
|
||||
::std::option::Option::Some(val) => val,
|
||||
::std::option::Option::None => return ::std::option::Option::None,
|
||||
});
|
||||
|
||||
($expr:expr => return) => (match $expr {
|
||||
::std::option::Option::Some(val) => val,
|
||||
::std::option::Option::None => return,
|
||||
})
|
||||
}
|
||||
|
||||
use glob::{glob, Paths, PatternError};
|
||||
|
||||
let hashstr : String = hash.into();
|
||||
let globstr = format!("{}/*-{}.imag", self.storepath, hashstr);
|
||||
debug!("glob({})", globstr);
|
||||
|
||||
let globs = glob(&globstr[..]);
|
||||
if globs.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = globs.unwrap().last();
|
||||
debug!("path = {:?}", path);
|
||||
|
||||
let pathbuf = try_some!(path);
|
||||
if pathbuf.is_err() { return None; }
|
||||
|
||||
let pathbuf_un = pathbuf.unwrap();
|
||||
let filename = pathbuf_un.file_name();
|
||||
let s = try_some!(filename).to_str();
|
||||
let string = String::from(try_some!(s));
|
||||
let id = try_some!(FileID::parse(&string));
|
||||
|
||||
debug!("Loaded ID = '{:?}'", id);
|
||||
|
||||
self.load_in_cache(m, parser, id)
|
||||
.map(|file| {
|
||||
debug!("Loaded File = '{:?}'", file);
|
||||
Some(file)
|
||||
}).unwrap_or(None)
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: FileID) -> bool {
|
||||
use std::fs::remove_file;
|
||||
|
||||
self.cache
|
||||
.borrow_mut()
|
||||
.remove(&id)
|
||||
.map(|file| {
|
||||
let idstr : String = id.into();
|
||||
let path = format!("{}/{}-{}.imag",
|
||||
self.storepath,
|
||||
file.deref().borrow().owner_name(),
|
||||
idstr);
|
||||
debug!("Removing file NOW: '{}'", path);
|
||||
remove_file(path).is_ok()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn load_for_module<HP>(&self, m: &Module, parser: &Parser<HP>)
|
||||
-> Vec<Rc<RefCell<File>>>
|
||||
where HP: FileHeaderParser
|
||||
{
|
||||
use glob::{glob, Paths, PatternError};
|
||||
|
||||
let globstr = format!("{}/{}-*.imag", self.storepath, m.name());
|
||||
let mut res = vec![];
|
||||
|
||||
glob(&globstr[..]).map(|paths| {
|
||||
for path in paths {
|
||||
if let Ok(pathbuf) = path {
|
||||
let fname = pathbuf.file_name().and_then(|s| s.to_str());
|
||||
fname.map(|s| {
|
||||
FileID::parse(&String::from(s)).map(|id| {
|
||||
self.load_in_cache(m, parser, id).map(|file| {
|
||||
res.push(file);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
fn get_new_file_id(&self) -> FileID {
|
||||
use uuid::Uuid;
|
||||
let hash = FileHash::from(Uuid::new_v4().to_hyphenated_string());
|
||||
FileID::new(FileIDType::UUID, hash)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,16 +88,11 @@ pub trait FileHeaderParser : Sized {
|
|||
fn write(&self, data: &FileHeaderData) -> Result<String, ParserError>;
|
||||
}
|
||||
|
||||
type TextTpl = (Option<String>, Option<String>);
|
||||
|
||||
pub struct Parser<HP>
|
||||
{
|
||||
pub struct Parser<HP> {
|
||||
headerp : HP,
|
||||
}
|
||||
|
||||
impl<HP> Parser<HP> where
|
||||
HP: FileHeaderParser,
|
||||
{
|
||||
impl<HP: FileHeaderParser> Parser<HP> {
|
||||
|
||||
pub fn new(headerp: HP) -> Parser<HP> {
|
||||
Parser {
|
||||
|
@ -105,8 +100,7 @@ impl<HP> Parser<HP> where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read(&self, s: String) -> Result<(FileHeaderData, String), ParserError>
|
||||
{
|
||||
pub fn read(&self, s: String) -> Result<(FileHeaderData, String), ParserError> {
|
||||
debug!("Reading into internal datastructure: '{}'", s);
|
||||
let divided = self.divide_text(&s);
|
||||
|
||||
|
@ -128,8 +122,7 @@ impl<HP> Parser<HP> where
|
|||
Ok((h_parseres, data.unwrap_or(String::new())))
|
||||
}
|
||||
|
||||
pub fn write(&self, tpl : (FileHeaderData, String)) -> Result<String, ParserError>
|
||||
{
|
||||
pub fn write(&self, tpl : (&FileHeaderData, &String)) -> Result<String, ParserError> {
|
||||
debug!("Parsing internal datastructure to String");
|
||||
let (header, data) = tpl;
|
||||
let h_text = try!(self.headerp.write(&header));
|
||||
|
@ -139,7 +132,7 @@ impl<HP> Parser<HP> where
|
|||
Ok(text)
|
||||
}
|
||||
|
||||
fn divide_text(&self, text: &String) -> Result<TextTpl, ParserError> {
|
||||
fn divide_text(&self, text: &String) -> Result<(Option<String>, Option<String>), ParserError> {
|
||||
let re = Regex::new(r"(?sm)^---$(.*)^---$(.*)").unwrap();
|
||||
|
||||
debug!("Splitting: '{}'", text);
|
||||
|
|
108
src/storage/path.rs
Normal file
108
src/storage/path.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use glob::glob;
|
||||
use glob::Paths;
|
||||
use glob::PatternError;
|
||||
|
||||
use storage::file::id::FileID;
|
||||
use storage::file::id_type::FileIDType;
|
||||
use storage::file::hash::FileHash;
|
||||
use module::Module;
|
||||
|
||||
/*
|
||||
* A path represents either a GLOB ("/tmp/store/module-*-*.imag" for example) or a full path
|
||||
*
|
||||
* It can be used to generate a File or iterate over some files
|
||||
*
|
||||
*/
|
||||
struct Path<'a> {
|
||||
|
||||
/*
|
||||
* The base part ("/tmp/")
|
||||
*/
|
||||
base: PathBuf,
|
||||
|
||||
/*
|
||||
* The store part ("/store/")
|
||||
*/
|
||||
store: PathBuf,
|
||||
|
||||
/*
|
||||
* The module
|
||||
*/
|
||||
module: &'a Module<'a>,
|
||||
|
||||
/*
|
||||
* The ID
|
||||
*/
|
||||
idtype: Option<FileIDType>,
|
||||
idhash: Option<FileHash>,
|
||||
id: Option<FileID>,
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Path<'a> {
|
||||
|
||||
fn new(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileID) -> Path<'a> {
|
||||
Path {
|
||||
base: base,
|
||||
store: store,
|
||||
module: m,
|
||||
idtype: Some(id.get_type()),
|
||||
idhash: Some(id.get_id()),
|
||||
id: Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_idtype(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileIDType) -> Path<'a> {
|
||||
Path {
|
||||
base: base,
|
||||
store: store,
|
||||
module: m,
|
||||
idtype: Some(id),
|
||||
idhash: None,
|
||||
id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_idhash(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileHash) -> Path<'a> {
|
||||
Path {
|
||||
base: base,
|
||||
store: store,
|
||||
module: m,
|
||||
idtype: None,
|
||||
idhash: Some(id),
|
||||
id: None,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Transform Path into str, so we can call glob() on it
|
||||
*/
|
||||
impl<'a> Into<String> for Path<'a> {
|
||||
|
||||
fn into(self) -> String {
|
||||
let mut s = self.base.clone();
|
||||
s.push(self.store.clone());
|
||||
s.push(self.module.name());
|
||||
if self.id.is_some() {
|
||||
let idstr : String = self.id.unwrap().into();
|
||||
s.push(idstr);
|
||||
} else {
|
||||
s.push("*");
|
||||
}
|
||||
s.set_extension("imag");
|
||||
s.to_str().unwrap_or("").into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Result<Paths, PatternError>> for Path<'a> {
|
||||
|
||||
fn into(self) -> Result<Paths, PatternError> {
|
||||
let s : String = self.into();
|
||||
glob(&s[..])
|
||||
}
|
||||
|
||||
}
|
102
src/ui/file.rs
102
src/ui/file.rs
|
@ -1,4 +1,7 @@
|
|||
use std::cell::RefCell;
|
||||
use std::iter::Iterator;
|
||||
use std::rc::Rc;
|
||||
use std::ops::Deref;
|
||||
|
||||
use storage::file::File;
|
||||
|
||||
|
@ -9,14 +12,29 @@ pub trait FilePrinter {
|
|||
/*
|
||||
* Print a single file
|
||||
*/
|
||||
fn print_file(&self, &File);
|
||||
fn print_file(&self, Rc<RefCell<File>>);
|
||||
|
||||
/*
|
||||
* Print a list of files
|
||||
*/
|
||||
fn print_files<'a, I: Iterator<Item = File<'a>>>(&self, files: I) {
|
||||
fn print_files<I: Iterator<Item = Rc<RefCell<File>>>>(&self, files: I) {
|
||||
for file in files {
|
||||
self.print_file(&file);
|
||||
self.print_file(file);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_file_custom<F>(&self, file: Rc<RefCell<File>>, f: &F)
|
||||
where F: Fn(Rc<RefCell<File>>) -> Vec<String>
|
||||
{
|
||||
info!("{}", f(file).join(" "));
|
||||
}
|
||||
|
||||
fn print_files_custom<F, I>(&self, files: I, f: &F)
|
||||
where I: Iterator<Item = Rc<RefCell<File>>>,
|
||||
F: Fn(Rc<RefCell<File>>) -> Vec<String>
|
||||
{
|
||||
for file in files {
|
||||
self.print_file_custom(file, f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,12 +52,20 @@ impl FilePrinter for DebugPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_file(&self, f: &File) {
|
||||
fn print_file(&self, f: Rc<RefCell<File>>) {
|
||||
if self.debug {
|
||||
debug!("[DebugPrinter] ->\n{:?}", f);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_file_custom<F>(&self, file: Rc<RefCell<File>>, f: &F)
|
||||
where F: Fn(Rc<RefCell<File>>) -> Vec<String>
|
||||
{
|
||||
if self.debug {
|
||||
debug!("[DebugPrinter] ->\n{:?}", f(file).join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct SimplePrinter {
|
||||
|
@ -56,13 +82,26 @@ impl FilePrinter for SimplePrinter {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_file(&self, f: &File) {
|
||||
fn print_file(&self, f: Rc<RefCell<File>>) {
|
||||
if self.debug {
|
||||
debug!("{:?}", f);
|
||||
} else if self.verbose {
|
||||
info!("{}", f);
|
||||
info!("{}", &*f.deref().borrow());
|
||||
} else {
|
||||
info!("[File]: {}", f.id());
|
||||
info!("[File]: {}", f.deref().borrow().id());
|
||||
}
|
||||
}
|
||||
|
||||
fn print_file_custom<F>(&self, file: Rc<RefCell<File>>, f: &F)
|
||||
where F: Fn(Rc<RefCell<File>>) -> Vec<String>
|
||||
{
|
||||
let s = f(file).join(" ");
|
||||
if self.debug {
|
||||
debug!("{:?}", s);
|
||||
} else if self.verbose {
|
||||
info!("{}", s);
|
||||
} else {
|
||||
info!("[File]: {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,11 +123,11 @@ impl FilePrinter for TablePrinter {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_file(&self, f: &File) {
|
||||
fn print_file(&self, f: Rc<RefCell<File>>) {
|
||||
self.sp.print_file(f);
|
||||
}
|
||||
|
||||
fn print_files<'a, I: Iterator<Item = File<'a>>>(&self, files: I) {
|
||||
fn print_files<I: Iterator<Item = Rc<RefCell<File>>>>(&self, files: I) {
|
||||
use prettytable::Table;
|
||||
use prettytable::row::Row;
|
||||
use prettytable::cell::Cell;
|
||||
|
@ -103,9 +142,9 @@ impl FilePrinter for TablePrinter {
|
|||
debug!("Printing file: {:?}", file);
|
||||
i += 1;
|
||||
let cell_i = Cell::new(&format!("{}", i)[..]);
|
||||
let cell_o = Cell::new(&format!("{}", file.owner().name())[..]);
|
||||
let cell_o = Cell::new(&format!("{}", file.deref().borrow().owner_name())[..]);
|
||||
|
||||
let id : String = file.id().into();
|
||||
let id : String = file.deref().borrow().id().clone().into();
|
||||
let cell_id = Cell::new(&id[..]);
|
||||
let row = Row::new(vec![cell_i, cell_o, cell_id]);
|
||||
tab.add_row(row);
|
||||
|
@ -119,4 +158,45 @@ impl FilePrinter for TablePrinter {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_files_custom<F, I>(&self, files: I, f: &F)
|
||||
where I: Iterator<Item = Rc<RefCell<File>>>,
|
||||
F: Fn(Rc<RefCell<File>>) -> Vec<String>
|
||||
{
|
||||
use prettytable::Table;
|
||||
use prettytable::row::Row;
|
||||
use prettytable::cell::Cell;
|
||||
|
||||
let titles = row!["#", "Module", "ID", "..."];
|
||||
|
||||
let mut tab = Table::new();
|
||||
tab.set_titles(titles);
|
||||
|
||||
let mut i = 0;
|
||||
for file in files {
|
||||
debug!("Printing file: {:?}", file);
|
||||
i += 1;
|
||||
let cell_i = Cell::new(&format!("{}", i)[..]);
|
||||
let cell_o = Cell::new(&format!("{}", file.deref().borrow().owner_name())[..]);
|
||||
|
||||
let id : String = file.deref().borrow().id().clone().into();
|
||||
let cell_id = Cell::new(&id[..]);
|
||||
|
||||
let mut row = Row::new(vec![cell_i, cell_o, cell_id]);
|
||||
|
||||
for cell in f(file).iter() {
|
||||
debug!("Adding custom cell: {:?}", cell);
|
||||
row.add_cell(Cell::new(&cell[..]))
|
||||
}
|
||||
|
||||
tab.add_row(row);
|
||||
}
|
||||
|
||||
if i != 0 {
|
||||
debug!("Printing {} table entries", i);
|
||||
tab.printstd();
|
||||
} else {
|
||||
debug!("Not printing table because there are zero entries");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue