diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index ab2ec363..00000000 --- a/src/error.rs +++ /dev/null @@ -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> { - } -} diff --git a/src/main.rs b/src/main.rs index 2d2102b3..55eac338 100644 --- a/src/main.rs +++ b/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; @@ -44,39 +40,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); - } else { - debug!("No subcommand"); - } - - module.shutdown(&rt); + let res = BM::new(&rt).exec(matches.subcommand_matches("bm")); + info!("BM exited with {}", res); } else { - // Err(ModuleError::mk("No commandline call")) info!("No commandline call...") } - info!("Hello, world!"); } diff --git a/src/module/bm/commands.rs b/src/module/bm/commands.rs deleted file mode 100644 index 2d6f8bad..00000000 --- a/src/module/bm/commands.rs +++ /dev/null @@ -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::>(); - - 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>, ModuleError> -{ - use module::helpers::utils::cli::get_tags; - - fn check_tags(tags: &Vec, 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::>() - .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 { - 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 { - 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") -} diff --git a/src/module/bm/header.rs b/src/module/bm/header.rs deleted file mode 100644 index 3cb4f75a..00000000 --- a/src/module/bm/header.rs +++ /dev/null @@ -1,30 +0,0 @@ -use module::helpers::header as headerhelpers; -use storage::file::header::data::FileHeaderData as FHD; -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() ] - } -} - -pub fn build_header(url: &String, tags: &Vec) -> FHD { - FHD::Map { - keys: vec![ - FHD::Key { - name: String::from("URL"), - value: Box::new(FHD::Text(url.clone())) - }, - FHD::Key { - name: String::from("TAGS"), - value: Box::new(headerhelpers::tags::data::build_tag_array(tags)) - } - ] - } -} - -pub fn get_tags_from_header(header: &FHD) -> Vec { - headerhelpers::tags::data::get_tags_from_header(header) -} - diff --git a/src/module/bm/mod.rs b/src/module/bm/mod.rs deleted file mode 100644 index 08502a77..00000000 --- a/src/module/bm/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -mod header; -mod commands; - -use std::fmt::{Debug, Formatter}; -use std::fmt::Result as FMTResult; - -use module::{CommandMap, Module, ModuleResult}; -use runtime::Runtime; -use self::commands::*; - -pub struct BMModule { - path: Option, -} - -const CALLNAMES : &'static [&'static str] = &[ "bm", "bookmark" ]; - -impl BMModule { - - pub fn new(rt : &Runtime) -> BMModule { - BMModule { - path: None - } - } - -} - -impl Module for BMModule { - - fn callnames(&self) -> &'static [&'static str] { - CALLNAMES - } - - 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 { - - fn fmt(&self, fmt: &mut Formatter) -> FMTResult { - write!(fmt, "[Module][BM]"); - Ok(()) - } - -} diff --git a/src/module/mod.rs b/src/module/mod.rs index d298d832..7e2462e1 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -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>, -} - -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]; - fn name(&self) -> &'static str; - fn shutdown(&self, rt : &Runtime) -> ModuleResult; - - fn get_commands(&self, rt: &Runtime) -> CommandMap; - + fn new(rt: &Runtime) -> Self; + fn exec(&self, matches: &ArgMatches) -> bool; } diff --git a/src/storage/file/id.rs b/src/storage/file/id.rs index a95043d1..c839fb25 100644 --- a/src/storage/file/id.rs +++ b/src/storage/file/id.rs @@ -64,10 +64,6 @@ impl FileID { } } - pub fn is_valid(&self) -> bool { - self.id.is_some() - } - pub fn get_type(&self) -> FileIDType { self.id_type.clone() } diff --git a/src/storage/file/mod.rs b/src/storage/file/mod.rs index baa40002..9a11abb6 100644 --- a/src/storage/file/mod.rs +++ b/src/storage/file/mod.rs @@ -6,6 +6,7 @@ use regex::Regex; pub mod id; pub mod header; +pub mod hash; use module::Module; use storage::file::id::*; diff --git a/src/storage/mod.rs b/src/storage/mod.rs deleted file mode 100644 index 96fe6968..00000000 --- a/src/storage/mod.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::error::Error; -use std::fmt::Result as FMTResult; -use std::fmt::{Display, Formatter}; -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}; - -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}; - -pub type BackendOperationResult = Result; - -pub struct Storage { - basepath: String, - storepath: String, -} - -impl Storage { - - pub fn new(rt: &Runtime) -> BackendOperationResult { - 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 iter_ids(&self, m: &Module) -> Result, 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) - }) - } - - pub fn iter_files<'a, HP>(&self, m: &'a Module, p: &Parser) - -> Result>, StorageError> - 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(&self, f: File, p: &Parser) -> 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(&self, f: File, p: &Parser) -> 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); - - 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(())) - } - - /* - * 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) -> Option> - where HP: FileHeaderParser - { - debug!("Searching for file with id '{}'", 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 - } - } - } - - 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!() - } - - 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))) - }) - } - - 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 { - 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" - } - - 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, - p: &Parser, - ids: IntoIter) - -> Vec> - where HP: FileHeaderParser - { - ids.filter_map(|id| self.get_file_by_id(m, &id, p)) - .collect::>() - } - -} - -#[derive(Debug)] -pub struct StorageError { - pub action: String, // The file system action in words - pub desc: String, // A short description - pub data_dump: Option, // Data dump, if any - pub caused_by: Option>, // caused from this error -} - -impl StorageError { - - fn new(action: S, - desc: S, - data: Option, - cause: Option>) - -> StorageError - where S: Into - { - StorageError { - action: action.into(), - desc: desc.into(), - data_dump: data, - caused_by: None, - } - } - -} - -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) -> Result - 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)))) - }) -} - -fn globlist_to_file_id_vec(globlist: Paths) -> Vec { - globlist.filter_map(Result::ok) - .map(|pbuf| FileID::from(&pbuf)) - .collect::>() -} - diff --git a/src/storage/parser.rs b/src/storage/parser.rs index c627e56a..266f6a3d 100644 --- a/src/storage/parser.rs +++ b/src/storage/parser.rs @@ -88,16 +88,11 @@ pub trait FileHeaderParser : Sized { fn write(&self, data: &FileHeaderData) -> Result; } -type TextTpl = (Option, Option); - -pub struct Parser -{ +pub struct Parser { headerp : HP, } -impl Parser where - HP: FileHeaderParser, -{ +impl Parser { pub fn new(headerp: HP) -> Parser { Parser { @@ -105,8 +100,7 @@ impl Parser 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 Parser where Ok((h_parseres, data.unwrap_or(String::new()))) } - pub fn write(&self, tpl : (FileHeaderData, String)) -> Result - { + pub fn write(&self, tpl : (FileHeaderData, String)) -> Result { debug!("Parsing internal datastructure to String"); let (header, data) = tpl; let h_text = try!(self.headerp.write(&header)); @@ -139,7 +132,7 @@ impl Parser where Ok(text) } - fn divide_text(&self, text: &String) -> Result { + fn divide_text(&self, text: &String) -> Result<(Option, Option), ParserError> { let re = Regex::new(r"(?sm)^---$(.*)^---$(.*)").unwrap(); debug!("Splitting: '{}'", text);