Merge pull request #113 from TheNeikos/add-store_retrieve
Implement Store::retrieve
This commit is contained in:
commit
fa2f1068d4
6 changed files with 475 additions and 373 deletions
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* EntryContent type
|
|
||||||
*/
|
|
||||||
pub type EntryContent = String;
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::fmt::Error as FmtError;
|
use std::fmt::Error as FmtError;
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
use std::fmt;
|
||||||
|
use std::convert::From;
|
||||||
|
use toml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kind of store error
|
* Kind of store error
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum StoreErrorKind {
|
pub enum StoreErrorKind {
|
||||||
FileError,
|
FileError,
|
||||||
IdLocked,
|
IdLocked,
|
||||||
|
@ -15,9 +17,13 @@ pub enum StoreErrorKind {
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
FileNotFound,
|
FileNotFound,
|
||||||
FileNotCreated,
|
FileNotCreated,
|
||||||
|
IoError,
|
||||||
StorePathExists,
|
StorePathExists,
|
||||||
StorePathCreate,
|
StorePathCreate,
|
||||||
// maybe more
|
LockPoisoned,
|
||||||
|
EntryAlreadyBorrowed,
|
||||||
|
MalformedEntry,
|
||||||
|
// maybe more
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
|
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::OutOfMemory => "Out of Memory",
|
||||||
&StoreErrorKind::FileNotFound => "File corresponding to ID not found",
|
&StoreErrorKind::FileNotFound => "File corresponding to ID not found",
|
||||||
&StoreErrorKind::FileNotCreated => "File corresponding to ID could not be created",
|
&StoreErrorKind::FileNotCreated => "File corresponding to ID could not be created",
|
||||||
|
&StoreErrorKind::IoError => "File Error",
|
||||||
&StoreErrorKind::StorePathExists => "Store path exists",
|
&StoreErrorKind::StorePathExists => "Store path exists",
|
||||||
&StoreErrorKind::StorePathCreate => "Store path create",
|
&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<Box<Error>>)
|
pub fn new(errtype: StoreErrorKind, cause: Option<Box<Error>>)
|
||||||
-> StoreError
|
-> StoreError
|
||||||
{
|
{
|
||||||
StoreError {
|
StoreError {
|
||||||
err_type: errtype,
|
err_type: errtype,
|
||||||
cause: cause,
|
cause: cause,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the error type of this StoreError
|
* Get the error type of this StoreError
|
||||||
|
@ -95,3 +106,78 @@ impl Error for StoreError {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ParserError> 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<Box<Error>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserError {
|
||||||
|
|
||||||
|
pub fn new(k: ParserErrorKind, cause: Option<Box<Error>>) -> 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<Box<Error>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParserError {
|
|
||||||
|
|
||||||
pub fn new(k: ParserErrorKind, cause: Option<Box<Error>>) -> 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<V> = RResult<V, error::ParserError>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<EntryHeader> {
|
|
||||||
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<Table> {
|
|
||||||
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<String, Value> {
|
|
||||||
let mut m = BTreeMap::new();
|
|
||||||
|
|
||||||
m.insert(String::from("imag"), {
|
|
||||||
let mut imag_map = BTreeMap::<String, Value>::new();
|
|
||||||
|
|
||||||
imag_map.insert(String::from("version"), Value::String(version!()));
|
|
||||||
imag_map.insert(String::from("links"), Value::Array(vec![]));
|
|
||||||
|
|
||||||
Value::Table(imag_map)
|
|
||||||
});
|
|
||||||
|
|
||||||
m
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,10 +6,7 @@ extern crate toml;
|
||||||
extern crate semver;
|
extern crate semver;
|
||||||
|
|
||||||
pub mod storeid;
|
pub mod storeid;
|
||||||
pub mod content;
|
|
||||||
pub mod entry;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod header;
|
|
||||||
pub mod store;
|
pub mod store;
|
||||||
mod lazyfile;
|
mod lazyfile;
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,25 @@ use std::path::PathBuf;
|
||||||
use std::result::Result as RResult;
|
use std::result::Result as RResult;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
|
||||||
use fs2::FileExt;
|
use fs2::FileExt;
|
||||||
|
use toml::{Table, Value};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use entry::Entry;
|
use error::{ParserErrorKind, ParserError};
|
||||||
use error::{StoreError, StoreErrorKind};
|
use error::{StoreError, StoreErrorKind};
|
||||||
use storeid::StoreId;
|
use storeid::StoreId;
|
||||||
|
use lazyfile::LazyFile;
|
||||||
|
|
||||||
/// The Result Type returned by any interaction with the store that could fail
|
/// The Result Type returned by any interaction with the store that could fail
|
||||||
pub type Result<T> = RResult<T, StoreError>;
|
pub type Result<T> = RResult<T, StoreError>;
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
enum StoreEntryPresence {
|
enum StoreEntryStatus {
|
||||||
Present,
|
Present,
|
||||||
Borrowed
|
Borrowed
|
||||||
}
|
}
|
||||||
|
@ -24,16 +31,34 @@ enum StoreEntryPresence {
|
||||||
/// A store entry, depending on the option type it is either borrowed currently
|
/// A store entry, depending on the option type it is either borrowed currently
|
||||||
/// or not.
|
/// or not.
|
||||||
struct StoreEntry {
|
struct StoreEntry {
|
||||||
file: File,
|
id: StoreId,
|
||||||
entry: StoreEntryPresence
|
file: LazyFile,
|
||||||
|
status: StoreEntryStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl StoreEntry {
|
impl StoreEntry {
|
||||||
/// The entry is currently borrowed, meaning that some thread is currently
|
/// The entry is currently borrowed, meaning that some thread is currently
|
||||||
/// mutating it
|
/// mutating it
|
||||||
fn is_borrowed(&self) -> bool {
|
fn is_borrowed(&self) -> bool {
|
||||||
self.entry == StoreEntryPresence::Borrowed
|
self.status == StoreEntryStatus::Borrowed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry(&mut self) -> Result<Entry> {
|
||||||
|
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
|
/// Borrow a given Entry. When the `FileLockEntry` is either `update`d or
|
||||||
/// dropped, the new Entry is written to disk
|
/// dropped, the new Entry is written to disk
|
||||||
pub fn retrieve<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
|
pub fn retrieve<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
|
||||||
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
|
/// Return the `FileLockEntry` and write to disk
|
||||||
|
@ -127,11 +159,9 @@ impl Drop for Store {
|
||||||
/**
|
/**
|
||||||
* Unlock all files on drop
|
* Unlock all files on drop
|
||||||
*
|
*
|
||||||
* TODO: Error message when file cannot be unlocked?
|
* TODO: Unlock them
|
||||||
*/
|
*/
|
||||||
fn drop(&mut self) {
|
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()
|
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<V> = RResult<V, ParserError>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<EntryHeader> {
|
||||||
|
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<String, Value> {
|
||||||
|
let mut m = BTreeMap::new();
|
||||||
|
|
||||||
|
m.insert(String::from("imag"), {
|
||||||
|
let mut imag_map = BTreeMap::<String, Value>::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<Table> {
|
||||||
|
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<Entry> {
|
||||||
|
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<Entry> {
|
||||||
|
let re = Regex::new(r"(?smx)
|
||||||
|
^---$
|
||||||
|
(?P<header>.*) # Header
|
||||||
|
^---$\n
|
||||||
|
(?P<content>.*) # 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue