diff --git a/libimagstore/src/content.rs b/libimagstore/src/content.rs deleted file mode 100644 index ade530f1..00000000 --- a/libimagstore/src/content.rs +++ /dev/null @@ -1,4 +0,0 @@ -/** - * EntryContent type - */ -pub type EntryContent = String; diff --git a/libimagstore/src/entry.rs b/libimagstore/src/entry.rs deleted file mode 100644 index b463a81e..00000000 --- a/libimagstore/src/entry.rs +++ /dev/null @@ -1,40 +0,0 @@ -use header::EntryHeader; -use content::EntryContent; -use storeid::StoreId; - -/** - * An Entry of the store - * - * Contains location, header and content part. - */ -#[derive(Debug, Clone)] -pub struct Entry { - location: StoreId, - header: EntryHeader, - content: EntryContent, -} - -impl Entry { - - pub fn get_location(&self) -> &StoreId { - &self.location - } - - pub fn get_header(&self) -> &EntryHeader { - &self.header - } - - pub fn get_header_mut(&mut self) -> &mut EntryHeader { - &mut self.header - } - - pub fn get_content(&self) -> &EntryContent { - &self.content - } - - pub fn get_content_mut(&mut self) -> &mut EntryContent { - &mut self.content - } - -} - diff --git a/libimagstore/src/error.rs b/libimagstore/src/error.rs index 9dc48bb5..9cd19814 100644 --- a/libimagstore/src/error.rs +++ b/libimagstore/src/error.rs @@ -1,13 +1,15 @@ use std::error::Error; -use std::fmt::Display; -use std::fmt::Formatter; use std::fmt::Error as FmtError; use std::clone::Clone; +use std::fmt::{Debug, Display, Formatter}; +use std::fmt; +use std::convert::From; +use toml; /** * Kind of store error */ -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum StoreErrorKind { FileError, IdLocked, @@ -15,9 +17,13 @@ pub enum StoreErrorKind { OutOfMemory, FileNotFound, FileNotCreated, + IoError, StorePathExists, StorePathCreate, - // maybe more + LockPoisoned, + EntryAlreadyBorrowed, + MalformedEntry, + // maybe more } fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str { @@ -28,8 +34,13 @@ fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str { &StoreErrorKind::OutOfMemory => "Out of Memory", &StoreErrorKind::FileNotFound => "File corresponding to ID not found", &StoreErrorKind::FileNotCreated => "File corresponding to ID could not be created", + &StoreErrorKind::IoError => "File Error", &StoreErrorKind::StorePathExists => "Store path exists", &StoreErrorKind::StorePathCreate => "Store path create", + &StoreErrorKind::LockPoisoned + => "The internal Store Lock has been poisoned", + &StoreErrorKind::EntryAlreadyBorrowed => "Entry is already borrowed", + &StoreErrorKind::MalformedEntry => "Entry has invalid formatting, missing header", } } @@ -58,12 +69,12 @@ impl StoreError { */ pub fn new(errtype: StoreErrorKind, cause: Option>) -> StoreError - { - StoreError { - err_type: errtype, - cause: cause, + { + StoreError { + err_type: errtype, + cause: cause, + } } - } /** * Get the error type of this StoreError @@ -95,3 +106,78 @@ impl Error for StoreError { } +impl From for StoreError { + fn from(ps: ParserError) -> StoreError { + StoreError { + err_type: StoreErrorKind::MalformedEntry, + cause: Some(Box::new(ps)), + } + } +} + +impl From<::std::io::Error> for StoreError { + fn from(ps: ::std::io::Error) -> StoreError { + StoreError { + err_type: StoreErrorKind::IoError, + cause: Some(Box::new(ps)), + } + } +} + +#[derive(Clone)] +pub enum ParserErrorKind { + TOMLParserErrors, + MissingMainSection, + MissingVersionInfo, +} + +pub struct ParserError { + kind: ParserErrorKind, + cause: Option>, +} + +impl ParserError { + + pub fn new(k: ParserErrorKind, cause: Option>) -> ParserError { + ParserError { + kind: k, + cause: cause, + } + } + +} + +impl Debug for ParserError { + + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + try!(write!(f, "{:?}", self.description())); + Ok(()) + } + +} + +impl Display for ParserError { + + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + try!(write!(f, "{}", self.description())); + Ok(()) + } + +} + +impl Error for ParserError { + + fn description(&self) -> &str { + match self.kind { + ParserErrorKind::TOMLParserErrors => "Several TOML-Parser-Errors", + ParserErrorKind::MissingMainSection => "Missing main section", + ParserErrorKind::MissingVersionInfo => "Missing version information in main section", + } + } + + fn cause(&self) -> Option<&Error> { + self.cause.as_ref().map(|e| &**e) + } + +} + diff --git a/libimagstore/src/header.rs b/libimagstore/src/header.rs deleted file mode 100644 index dc432da2..00000000 --- a/libimagstore/src/header.rs +++ /dev/null @@ -1,307 +0,0 @@ -use std::collections::BTreeMap; -use std::error::Error; -use std::result::Result as RResult; - -use toml::{Array, Table, Value}; -use version; - -use self::error::ParserErrorKind; -use self::error::ParserError; - -pub mod error { - use std::fmt::{Debug, Display, Formatter}; - use std::fmt; - use std::error::Error; - use toml; - - #[derive(Clone)] - pub enum ParserErrorKind { - TOMLParserErrors, - MissingMainSection, - MissingVersionInfo, - } - - pub struct ParserError { - kind: ParserErrorKind, - cause: Option>, - } - - impl ParserError { - - pub fn new(k: ParserErrorKind, cause: Option>) -> ParserError { - ParserError { - kind: k, - cause: cause, - } - } - - } - - impl Debug for ParserError { - - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - try!(write!(f, "{:?}", self.description())); - Ok(()) - } - - } - - impl Display for ParserError { - - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - try!(write!(f, "{}", self.description())); - Ok(()) - } - - } - - impl Error for ParserError { - - fn description(&self) -> &str { - match self.kind { - ParserErrorKind::TOMLParserErrors => "Several TOML-Parser-Errors", - ParserErrorKind::MissingMainSection => "Missing main section", - ParserErrorKind::MissingVersionInfo => "Missing version information in main section", - } - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - - } - -} - -/** - * EntryHeader - * - * This is basically a wrapper around toml::Table which provides convenience to the user of the - * librray. - */ -#[derive(Debug, Clone)] -pub struct EntryHeader { - toml: Table, -} - -pub type Result = RResult; - -/** - * Wrapper type around file header (TOML) object - */ -impl EntryHeader { - - /** - * Get a new header object with a already-filled toml table - * - * Default header values are inserted into the header object by default. - */ - pub fn new() -> EntryHeader { - EntryHeader { - toml: build_default_header(), - } - } - - /** - * Get the table which lives in the background - */ - pub fn toml(&self) -> &Table { - &self.toml - } - - pub fn parse(s: &str) -> Result { - use toml::Parser; - - let mut parser = Parser::new(s); - parser.parse() - .ok_or(ParserError::new(ParserErrorKind::TOMLParserErrors, None)) - .and_then(|t| verify_header_consistency(t)) - .map(|t| { - EntryHeader { - toml: t - } - }) - } - -} - -fn verify_header_consistency(t: Table) -> Result { - if !has_main_section(&t) { - Err(ParserError::new(ParserErrorKind::MissingMainSection, None)) - } else if !has_imag_version_in_main_section(&t) { - Err(ParserError::new(ParserErrorKind::MissingVersionInfo, None)) - } else { - Ok(t) - } -} - -fn has_main_section(t: &Table) -> bool { - t.contains_key("imag") && - match t.get("imag") { - Some(&Value::Table(_)) => true, - Some(_) => false, - None => false, - } -} - -fn has_imag_version_in_main_section(t: &Table) -> bool { - use semver::Version; - - match t.get("imag").unwrap() { - &Value::Table(ref sec) => { - sec.get("version") - .and_then(|v| { - match v { - &Value::String(ref s) => { - Some(Version::parse(&s[..]).is_ok()) - }, - _ => Some(false), - } - }) - .unwrap_or(false) - } - _ => false, - } -} - - -#[cfg(test)] -mod test { - use std::collections::BTreeMap; - - use toml::Value; - - #[test] - fn test_imag_section() { - use super::has_main_section; - - let mut map = BTreeMap::new(); - map.insert("imag".into(), Value::Table(BTreeMap::new())); - - assert!(has_main_section(&map)); - } - - #[test] - fn test_imag_invalid_section_type() { - use super::has_main_section; - - let mut map = BTreeMap::new(); - map.insert("imag".into(), Value::Boolean(false)); - - assert!(!has_main_section(&map)); - } - - #[test] - fn test_imag_abscent_main_section() { - use super::has_main_section; - - let mut map = BTreeMap::new(); - map.insert("not_imag".into(), Value::Boolean(false)); - - assert!(!has_main_section(&map)); - } - - #[test] - fn test_main_section_without_version() { - use super::has_imag_version_in_main_section; - - let mut map = BTreeMap::new(); - map.insert("imag".into(), Value::Table(BTreeMap::new())); - - assert!(!has_imag_version_in_main_section(&map)); - } - - #[test] - fn test_main_section_with_version() { - use super::has_imag_version_in_main_section; - - let mut map = BTreeMap::new(); - let mut sub = BTreeMap::new(); - sub.insert("version".into(), Value::String("0.0.0".into())); - map.insert("imag".into(), Value::Table(sub)); - - assert!(has_imag_version_in_main_section(&map)); - } - - #[test] - fn test_main_section_with_version_in_wrong_type() { - use super::has_imag_version_in_main_section; - - let mut map = BTreeMap::new(); - let mut sub = BTreeMap::new(); - sub.insert("version".into(), Value::Boolean(false)); - map.insert("imag".into(), Value::Table(sub)); - - assert!(!has_imag_version_in_main_section(&map)); - } - - #[test] - fn test_verification_good() { - use super::verify_header_consistency; - - let mut header = BTreeMap::new(); - let sub = { - let mut sub = BTreeMap::new(); - sub.insert("version".into(), Value::String(String::from("0.0.0"))); - - Value::Table(sub) - }; - - header.insert("imag".into(), sub); - - assert!(verify_header_consistency(header).is_ok()); - } - - #[test] - fn test_verification_invalid_versionstring() { - use super::verify_header_consistency; - - let mut header = BTreeMap::new(); - let sub = { - let mut sub = BTreeMap::new(); - sub.insert("version".into(), Value::String(String::from("000"))); - - Value::Table(sub) - }; - - header.insert("imag".into(), sub); - - assert!(!verify_header_consistency(header).is_ok()); - } - - - #[test] - fn test_verification_current_version() { - use version; - - use super::verify_header_consistency; - - let mut header = BTreeMap::new(); - let sub = { - let mut sub = BTreeMap::new(); - sub.insert("version".into(), Value::String(version!())); - - Value::Table(sub) - }; - - header.insert("imag".into(), sub); - - assert!(verify_header_consistency(header).is_ok()); - } -} - -fn build_default_header() -> BTreeMap { - let mut m = BTreeMap::new(); - - m.insert(String::from("imag"), { - let mut imag_map = BTreeMap::::new(); - - imag_map.insert(String::from("version"), Value::String(version!())); - imag_map.insert(String::from("links"), Value::Array(vec![])); - - Value::Table(imag_map) - }); - - m -} - diff --git a/libimagstore/src/lib.rs b/libimagstore/src/lib.rs index 69665320..b6152caa 100644 --- a/libimagstore/src/lib.rs +++ b/libimagstore/src/lib.rs @@ -6,10 +6,7 @@ extern crate toml; extern crate semver; pub mod storeid; -pub mod content; -pub mod entry; pub mod error; -pub mod header; pub mod store; mod lazyfile; diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 74ffcc58..f6e78611 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -5,18 +5,25 @@ use std::path::PathBuf; use std::result::Result as RResult; use std::sync::Arc; use std::sync::RwLock; +use std::error::Error; +use std::collections::BTreeMap; + use fs2::FileExt; +use toml::{Table, Value}; +use regex::Regex; -use entry::Entry; +use error::{ParserErrorKind, ParserError}; use error::{StoreError, StoreErrorKind}; use storeid::StoreId; +use lazyfile::LazyFile; /// The Result Type returned by any interaction with the store that could fail pub type Result = RResult; + #[derive(PartialEq)] -enum StoreEntryPresence { +enum StoreEntryStatus { Present, Borrowed } @@ -24,16 +31,34 @@ enum StoreEntryPresence { /// A store entry, depending on the option type it is either borrowed currently /// or not. struct StoreEntry { - file: File, - entry: StoreEntryPresence + id: StoreId, + file: LazyFile, + status: StoreEntryStatus, } - impl StoreEntry { /// The entry is currently borrowed, meaning that some thread is currently /// mutating it fn is_borrowed(&self) -> bool { - self.entry == StoreEntryPresence::Borrowed + self.status == StoreEntryStatus::Borrowed + } + + fn get_entry(&mut self) -> Result { + if !self.is_borrowed() { + let file = self.file.get_file_mut(); + if let Err(err) = file { + if err.err_type() == StoreErrorKind::FileNotFound { + Ok(Entry::new(self.id.clone())) + } else { + Err(err) + } + } else { + // TODO: + Entry::from_file(self.id.clone(), file.unwrap()) + } + } else { + return Err(StoreError::new(StoreErrorKind::EntryAlreadyBorrowed, None)) + } } } @@ -83,7 +108,14 @@ impl Store { /// Borrow a given Entry. When the `FileLockEntry` is either `update`d or /// dropped, the new Entry is written to disk pub fn retrieve<'a>(&'a self, id: StoreId) -> Result> { - unimplemented!(); + let hsmap = self.entries.write(); + if hsmap.is_err() { + return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) + } + hsmap.unwrap().get_mut(&id) + .ok_or(StoreError::new(StoreErrorKind::IdNotFound, None)) + .and_then(|store_entry| store_entry.get_entry()) + .and_then(|entry| Ok(FileLockEntry::new(self, entry, id))) } /// Return the `FileLockEntry` and write to disk @@ -127,11 +159,9 @@ impl Drop for Store { /** * Unlock all files on drop * - * TODO: Error message when file cannot be unlocked? + * TODO: Unlock them */ fn drop(&mut self) { - self.entries.write().unwrap() - .iter().map(|f| f.1.file.unlock()); } } @@ -172,3 +202,343 @@ impl<'a> Drop for FileLockEntry<'a> { self.store._update(self).unwrap() } } + +/** + * EntryContent type + */ +pub type EntryContent = String; + +/** + * EntryHeader + * + * This is basically a wrapper around toml::Table which provides convenience to the user of the + * librray. + */ +#[derive(Debug, Clone)] +pub struct EntryHeader { + toml: Table, +} + +pub type EntryResult = RResult; + +/** + * Wrapper type around file header (TOML) object + */ +impl EntryHeader { + + pub fn new() -> EntryHeader { + EntryHeader { + toml: build_default_header() + } + } + + fn from_table(t: Table) -> EntryHeader { + EntryHeader { + toml: t + } + } + + /** + * Get the table which lives in the background + */ + pub fn toml(&self) -> &Table { + &self.toml + } + + pub fn parse(s: &str) -> EntryResult { + use toml::Parser; + + let mut parser = Parser::new(s); + parser.parse() + .ok_or(ParserError::new(ParserErrorKind::TOMLParserErrors, None)) + .and_then(|t| verify_header_consistency(t)) + .map(|t| EntryHeader::from_table(t)) + } + +} + +fn build_default_header() -> BTreeMap { + let mut m = BTreeMap::new(); + + m.insert(String::from("imag"), { + let mut imag_map = BTreeMap::::new(); + + imag_map.insert(String::from("version"), Value::String(version!())); + imag_map.insert(String::from("links"), Value::Array(vec![])); + + Value::Table(imag_map) + }); + + m +} + +fn verify_header_consistency(t: Table) -> EntryResult
{ + if !has_main_section(&t) { + Err(ParserError::new(ParserErrorKind::MissingMainSection, None)) + } else if !has_imag_version_in_main_section(&t) { + Err(ParserError::new(ParserErrorKind::MissingVersionInfo, None)) + } else { + Ok(t) + } +} + +fn has_main_section(t: &Table) -> bool { + t.contains_key("imag") && + match t.get("imag") { + Some(&Value::Table(_)) => true, + Some(_) => false, + None => false, + } +} + +fn has_imag_version_in_main_section(t: &Table) -> bool { + use semver::Version; + + match t.get("imag").unwrap() { + &Value::Table(ref sec) => { + sec.get("version") + .and_then(|v| { + match v { + &Value::String(ref s) => { + Some(Version::parse(&s[..]).is_ok()) + }, + _ => Some(false), + } + }) + .unwrap_or(false) + } + _ => false, + } +} + +/** + * An Entry of the store + * + * Contains location, header and content part. + */ +#[derive(Debug, Clone)] +pub struct Entry { + location: StoreId, + header: EntryHeader, + content: EntryContent, +} + +impl Entry { + + fn new(loc: StoreId) -> Entry { + Entry { + location: loc, + header: EntryHeader::new(), + content: EntryContent::new() + } + } + + fn from_file(loc: StoreId, file: &mut File) -> Result { + let text = { + use std::io::Read; + let mut s = String::new(); + try!(file.read_to_string(&mut s)); + s + }; + Self::from_str(loc, &text[..]) + } + + fn from_str(loc: StoreId, s: &str) -> Result { + let re = Regex::new(r"(?smx) + ^---$ + (?P
.*) # Header + ^---$\n + (?P.*) # Content + ").unwrap(); + + let matches = re.captures(s); + + if matches.is_none() { + return Err(StoreError::new(StoreErrorKind::MalformedEntry, None)); + } + + let matches = matches.unwrap(); + + let header = matches.name("header"); + let content = matches.name("content").unwrap_or(""); + + if header.is_none() { + return Err(StoreError::new(StoreErrorKind::MalformedEntry, None)); + } + + Ok(Entry { + location: loc, + header: try!(EntryHeader::parse(header.unwrap())), + content: content.into(), + }) + } + + pub fn get_location(&self) -> &StoreId { + &self.location + } + + pub fn get_header(&self) -> &EntryHeader { + &self.header + } + + pub fn get_header_mut(&mut self) -> &mut EntryHeader { + &mut self.header + } + + pub fn get_content(&self) -> &EntryContent { + &self.content + } + + pub fn get_content_mut(&mut self) -> &mut EntryContent { + &mut self.content + } + +} + + +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use toml::Value; + + #[test] + fn test_imag_section() { + use super::has_main_section; + + let mut map = BTreeMap::new(); + map.insert("imag".into(), Value::Table(BTreeMap::new())); + + assert!(has_main_section(&map)); + } + + #[test] + fn test_imag_invalid_section_type() { + use super::has_main_section; + + let mut map = BTreeMap::new(); + map.insert("imag".into(), Value::Boolean(false)); + + assert!(!has_main_section(&map)); + } + + #[test] + fn test_imag_abscent_main_section() { + use super::has_main_section; + + let mut map = BTreeMap::new(); + map.insert("not_imag".into(), Value::Boolean(false)); + + assert!(!has_main_section(&map)); + } + + #[test] + fn test_main_section_without_version() { + use super::has_imag_version_in_main_section; + + let mut map = BTreeMap::new(); + map.insert("imag".into(), Value::Table(BTreeMap::new())); + + assert!(!has_imag_version_in_main_section(&map)); + } + + #[test] + fn test_main_section_with_version() { + use super::has_imag_version_in_main_section; + + let mut map = BTreeMap::new(); + let mut sub = BTreeMap::new(); + sub.insert("version".into(), Value::String("0.0.0".into())); + map.insert("imag".into(), Value::Table(sub)); + + assert!(has_imag_version_in_main_section(&map)); + } + + #[test] + fn test_main_section_with_version_in_wrong_type() { + use super::has_imag_version_in_main_section; + + let mut map = BTreeMap::new(); + let mut sub = BTreeMap::new(); + sub.insert("version".into(), Value::Boolean(false)); + map.insert("imag".into(), Value::Table(sub)); + + assert!(!has_imag_version_in_main_section(&map)); + } + + #[test] + fn test_verification_good() { + use super::verify_header_consistency; + + let mut header = BTreeMap::new(); + let sub = { + let mut sub = BTreeMap::new(); + sub.insert("version".into(), Value::String(String::from("0.0.0"))); + + Value::Table(sub) + }; + + header.insert("imag".into(), sub); + + assert!(verify_header_consistency(header).is_ok()); + } + + #[test] + fn test_verification_invalid_versionstring() { + use super::verify_header_consistency; + + let mut header = BTreeMap::new(); + let sub = { + let mut sub = BTreeMap::new(); + sub.insert("version".into(), Value::String(String::from("000"))); + + Value::Table(sub) + }; + + header.insert("imag".into(), sub); + + assert!(!verify_header_consistency(header).is_ok()); + } + + + #[test] + fn test_verification_current_version() { + use version; + + use super::verify_header_consistency; + + let mut header = BTreeMap::new(); + let sub = { + let mut sub = BTreeMap::new(); + sub.insert("version".into(), Value::String(version!())); + + Value::Table(sub) + }; + + header.insert("imag".into(), sub); + + assert!(verify_header_consistency(header).is_ok()); + } + + static TEST_ENTRY : &'static str = "--- +[imag] +version = '0.0.3' +--- +Hai"; + + #[test] + fn test_entry_from_str() { + use super::Entry; + use std::path::PathBuf; + println!("{}", TEST_ENTRY); + let entry = Entry::from_str(PathBuf::from("/test/foo~1.3"), + TEST_ENTRY).unwrap(); + + assert_eq!(entry.content, "Hai"); + } + + +} + + +