Merge pull request #200 from matthiasbeyer/libimagstore/hooks
Libimagstore/hooks
This commit is contained in:
commit
68db3b4ce3
14 changed files with 950 additions and 10 deletions
|
@ -68,3 +68,99 @@ It also MUST contain a getter for this variable.
|
||||||
It MUST NOT contain a setter for this variable, as changing the store while the
|
It MUST NOT contain a setter for this variable, as changing the store while the
|
||||||
programm is running is not allowed.
|
programm is running is not allowed.
|
||||||
|
|
||||||
|
## Hook system {#sec:libstore:hooks}
|
||||||
|
|
||||||
|
The store library includes a hook system, which can be used to execute arbitrary
|
||||||
|
code before or after the store was accessed. The following hooks are available:
|
||||||
|
|
||||||
|
* `PreReadHook`
|
||||||
|
* `PostReadHook`
|
||||||
|
* `PreCreateHook`
|
||||||
|
* `PostCreateHook`
|
||||||
|
* `PreUpdateHook`
|
||||||
|
* `PostUpdateHook`
|
||||||
|
* `PreDeleteHook`
|
||||||
|
* `PostDeleteHook`
|
||||||
|
|
||||||
|
These are called "Hook positions" in the following.
|
||||||
|
|
||||||
|
Which are executed before or after the store action is executed. The `Pre`-Hooks
|
||||||
|
can deny the execution by returning an error. The `Post`-Hooks can (for the
|
||||||
|
appropriate store actions) alter the hook result.
|
||||||
|
|
||||||
|
Registering hooks with the store is implemented via functions on the `Store`
|
||||||
|
type itself. Hooks MUST NEVER be removed from the `Store` object during runtime,
|
||||||
|
only adding hooks to the store is allowed.
|
||||||
|
|
||||||
|
As the hooks are simply trait objects, one is able to implement arbitrary hooks,
|
||||||
|
for example
|
||||||
|
|
||||||
|
* Simple consistency-checks for the store
|
||||||
|
* Version control system adaption for the store (via git for example)
|
||||||
|
* Encryption of store entries (via gnupg for example)
|
||||||
|
* Automatic backup on every change to the store (via rsnapshot for example)
|
||||||
|
|
||||||
|
Some hooks MAY be shipped with the imag source distribution and be enabled by
|
||||||
|
default.
|
||||||
|
|
||||||
|
Execution order of the hooks is a not-yet-solved problem.
|
||||||
|
|
||||||
|
### Hook-Aspects {#sec:libstore:hooks:aspects}
|
||||||
|
|
||||||
|
Each hook can be assigned to an "Aspect". There MAY BE zero or more aspects for
|
||||||
|
each Hook position. Aspects can be sorted and configured via the configuration
|
||||||
|
file, whereas each aspect has its own configuration section:
|
||||||
|
|
||||||
|
```{#lst:hooks:aspects:cfg .toml .numberLines caption="Hook config section"}
|
||||||
|
[store]
|
||||||
|
|
||||||
|
// Defines order of aspects for the pre-read hook position
|
||||||
|
pre-read-aspects = [ "misc" ]
|
||||||
|
|
||||||
|
// Defines order of aspects for the post-read hook position
|
||||||
|
post-read-aspects = [ "decryption" ]
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// configuration for the "misc" hook aspect
|
||||||
|
[[aspects.misc]]
|
||||||
|
parallel-execution = true
|
||||||
|
|
||||||
|
// configuration for the "decryption" hook aspect
|
||||||
|
[[aspects.decryption]]
|
||||||
|
parallel-execution = false
|
||||||
|
```
|
||||||
|
|
||||||
|
Aspects are executed in the same order they appear in the configuration (in the
|
||||||
|
`pre-read-aspects = []` array, for example).
|
||||||
|
Aspects _could_ be sorted in different order for each hook position.
|
||||||
|
|
||||||
|
Aspect names are unique, so one aspect "misc" in "pre-read-aspects" is the
|
||||||
|
same as in "post-read-aspects" and both be configured via `aspects.misc`, though
|
||||||
|
they do not share hooks.
|
||||||
|
|
||||||
|
Aspects where parallel execution is enabled MAY BE executed in sequence if one
|
||||||
|
of the hooks wants mutable access to the data they hook into.
|
||||||
|
|
||||||
|
Hooks can then be assigned to one hook aspect. Hooks MUST never be assigned to
|
||||||
|
more than one hook aspect. Hooks which are not assigned to any aspect MUST never
|
||||||
|
be executed.
|
||||||
|
|
||||||
|
```{#lst:hooks:cfg .toml .numberLines caption="Hook configuration"}
|
||||||
|
[hooks]
|
||||||
|
|
||||||
|
// decrypt hook with gnupg. An appropriate "gnupg-encrypt" hook must be defined
|
||||||
|
// to be fully operational, of course
|
||||||
|
[[gnupg-decrypt]]
|
||||||
|
aspect = "decryption"
|
||||||
|
key = "0x123456789"
|
||||||
|
|
||||||
|
// version control hook. Sorted into aspect "misc" here.
|
||||||
|
[[git]]
|
||||||
|
aspect = "misc"
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Hooks MAY HAVE arbitrary configuration keys.
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,13 @@ impl Configuration {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn store_config(&self) -> Option<&Value> {
|
||||||
|
match &self.config {
|
||||||
|
&Value::Table(ref tabl) => tabl.get("store"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_verbosity(v: &Value) -> bool {
|
fn get_verbosity(v: &Value) -> bool {
|
||||||
|
|
|
@ -68,7 +68,14 @@ impl<'a> Runtime<'a> {
|
||||||
Some(cfg.unwrap())
|
Some(cfg.unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
Store::new(storepath).map(|store| {
|
let store_config = {
|
||||||
|
match &cfg {
|
||||||
|
&Some(ref c) => c.store_config().map(|c| c.clone()),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Store::new(storepath, store_config).map(|store| {
|
||||||
Runtime {
|
Runtime {
|
||||||
cli_matches: matches,
|
cli_matches: matches,
|
||||||
configuration: cfg,
|
configuration: cfg,
|
||||||
|
|
|
@ -12,6 +12,7 @@ regex = "0.1.54"
|
||||||
semver = "0.2"
|
semver = "0.2"
|
||||||
toml = "0.1.25"
|
toml = "0.1.25"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
crossbeam = "0.2.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3.4"
|
tempdir = "0.3.4"
|
||||||
|
|
247
libimagstore/src/configuration.rs
Normal file
247
libimagstore/src/configuration.rs
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
use toml::Value;
|
||||||
|
use hook::position::HookPosition;
|
||||||
|
|
||||||
|
/// Check whether the configuration is valid for the store
|
||||||
|
///
|
||||||
|
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
|
||||||
|
/// fail.
|
||||||
|
///
|
||||||
|
/// It checks whether the configuration looks like the store wants it to be:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [store]
|
||||||
|
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
|
||||||
|
///
|
||||||
|
/// [[aspects.misc]]
|
||||||
|
/// parallel = true
|
||||||
|
/// [[aspects.encryption]]
|
||||||
|
/// parallel = false
|
||||||
|
/// [[aspects.version-control]]
|
||||||
|
/// parallel = false
|
||||||
|
///
|
||||||
|
/// [[hooks.gnupg]]
|
||||||
|
/// aspect = "encryption"
|
||||||
|
/// key = "0x123456789"
|
||||||
|
///
|
||||||
|
/// [[hooks.git]]
|
||||||
|
/// aspect = "version-control"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// It checks:
|
||||||
|
/// * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
|
||||||
|
/// maps)
|
||||||
|
/// * Whether each aspect configuration has a "parallel = <Boolean>" setting
|
||||||
|
/// * Whether each hook congfiguration has a "aspect = <String>" setting
|
||||||
|
///
|
||||||
|
/// It does NOT check:
|
||||||
|
/// * Whether all aspects which are used in the hook configuration are also configured
|
||||||
|
///
|
||||||
|
/// No configuration is a valid configuration, as the store will use the most conservative settings
|
||||||
|
/// automatically. This has also performance impact, as all hooks run in no-parallel mode then.
|
||||||
|
/// You have been warned!
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub fn config_is_valid(config: &Option<Value>) -> bool {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
if config.is_none() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_key_with_map(v: &BTreeMap<String, Value>, key: &str) -> bool {
|
||||||
|
v.get(key).map(|t| match t { &Value::Table(_) => true, _ => false }).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
|
||||||
|
v.get(key)
|
||||||
|
.map(|t| match t {
|
||||||
|
&Value::Array(ref a) => a.iter().all(|elem| {
|
||||||
|
match elem {
|
||||||
|
&Value::String(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => false
|
||||||
|
}).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that
|
||||||
|
/// * the top-level configuration
|
||||||
|
/// * is a table
|
||||||
|
/// * where all entries of a key `section` (eg. "hooks" or "aspects")
|
||||||
|
/// * Are maps
|
||||||
|
/// * where each has a key `key` (eg. "aspect" or "parallel")
|
||||||
|
/// * which fullfills constraint `f` (typecheck)
|
||||||
|
fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
|
||||||
|
section: &str,
|
||||||
|
key: &str,
|
||||||
|
f: F)
|
||||||
|
-> bool
|
||||||
|
where F: Fn(&Value) -> bool
|
||||||
|
{
|
||||||
|
store_config.get(section) // The store config has the section `section`
|
||||||
|
.map(|section_table| {
|
||||||
|
match section_table { // which is
|
||||||
|
&Value::Table(ref section_table) => // a table
|
||||||
|
section_table
|
||||||
|
.values() // which has values,
|
||||||
|
.all(|cfg| { // and all of these values
|
||||||
|
match cfg {
|
||||||
|
&Value::Table(ref hook_config) => { // are tables
|
||||||
|
hook_config.get(key) // with a key
|
||||||
|
// fullfilling this constraint
|
||||||
|
.map(|hook_aspect| f(&hook_aspect))
|
||||||
|
.unwrap_or(false)
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
match config {
|
||||||
|
&Some(Value::Table(ref t)) => {
|
||||||
|
has_key_with_string_ary(t, "pre-read-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "post-read-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "pre-create-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "post-create-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "pre-retrieve-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "post-retrieve-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "pre-update-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "post-update-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "pre-delete-hook-aspects") &&
|
||||||
|
has_key_with_string_ary(t, "post-delete-hook-aspects") &&
|
||||||
|
|
||||||
|
// The section "hooks" has maps which have a key "aspect" which has a value of type
|
||||||
|
// String
|
||||||
|
check_all_inner_maps_have_key_with(t, "hooks", "aspect", |asp| {
|
||||||
|
match asp { &Value::String(_) => true, _ => false }
|
||||||
|
}) &&
|
||||||
|
|
||||||
|
// The section "aspects" has maps which have a key "parllel" which has a value of type
|
||||||
|
// Boolean
|
||||||
|
check_all_inner_maps_have_key_with(t, "aspects", "parallel", |asp| {
|
||||||
|
match asp { &Value::Boolean(_) => true, _ => false, }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pre_read_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("pre-read-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_post_read_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("post-read-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pre_create_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("pre-create-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_post_create_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("post-create-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pre_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("pre-retrieve-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_post_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("post-retrieve-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pre_update_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("pre-update-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_post_update_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("post-update-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pre_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("pre-delete-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_post_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
|
||||||
|
get_aspect_names_for_aspect_position("post-delete-hook-aspects", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AspectConfig {
|
||||||
|
parallel: bool,
|
||||||
|
config: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AspectConfig {
|
||||||
|
|
||||||
|
pub fn new(init: Value) -> AspectConfig {
|
||||||
|
let parallel = AspectConfig::is_parallel(&init);
|
||||||
|
AspectConfig {
|
||||||
|
config: init,
|
||||||
|
parallel: parallel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self) -> &Value {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parallel(init: &Value) -> bool {
|
||||||
|
match init {
|
||||||
|
&Value::Table(ref t) =>
|
||||||
|
t.get("parallel")
|
||||||
|
.map(|value| {
|
||||||
|
match value {
|
||||||
|
&Value::Boolean(b) => b,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the aspect configuration for an aspect.
|
||||||
|
///
|
||||||
|
/// Pass the store configuration object, this searches in `[aspects][<aspect_name>]`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if one of the keys in the chain is not available
|
||||||
|
pub fn get_for(v: &Option<Value>, a_name: String) -> Option<AspectConfig> {
|
||||||
|
match v {
|
||||||
|
&Some(Value::Table(ref tabl)) => tabl.get(&a_name[..])
|
||||||
|
.map(|asp| AspectConfig::new(asp.clone())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Option<Value>) -> Vec<String> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
match value {
|
||||||
|
&Some(Value::Table(ref t)) => {
|
||||||
|
match t.get(config_name) {
|
||||||
|
Some(&Value::Array(ref a)) => {
|
||||||
|
for elem in a {
|
||||||
|
match elem {
|
||||||
|
&Value::String(ref s) => v.push(s.clone()),
|
||||||
|
_ => warn!("Non-String in configuration, inside '{}'", config_name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => warn!("'{}' configuration key should contain Array, does not", config_name),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
&None => warn!("No store configuration"),
|
||||||
|
_ => warn!("Configuration is not a table"),
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::convert::From;
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum StoreErrorKind {
|
pub enum StoreErrorKind {
|
||||||
|
ConfigurationError,
|
||||||
FileError,
|
FileError,
|
||||||
IdLocked,
|
IdLocked,
|
||||||
IdNotFound,
|
IdNotFound,
|
||||||
|
@ -27,11 +28,16 @@ pub enum StoreErrorKind {
|
||||||
HeaderPathTypeFailure,
|
HeaderPathTypeFailure,
|
||||||
HeaderKeyNotFound,
|
HeaderKeyNotFound,
|
||||||
HeaderTypeFailure,
|
HeaderTypeFailure,
|
||||||
|
HookRegisterError,
|
||||||
|
HookExecutionError,
|
||||||
|
PreHookExecuteError,
|
||||||
|
PostHookExecuteError,
|
||||||
// maybe more
|
// maybe more
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
|
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
|
||||||
match e {
|
match e {
|
||||||
|
&StoreErrorKind::ConfigurationError => "Store Configuration Error",
|
||||||
&StoreErrorKind::FileError => "File Error",
|
&StoreErrorKind::FileError => "File Error",
|
||||||
&StoreErrorKind::IdLocked => "ID locked",
|
&StoreErrorKind::IdLocked => "ID locked",
|
||||||
&StoreErrorKind::IdNotFound => "ID not found",
|
&StoreErrorKind::IdNotFound => "ID not found",
|
||||||
|
@ -50,6 +56,10 @@ fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
|
||||||
&StoreErrorKind::HeaderPathTypeFailure => "Header has wrong type for path",
|
&StoreErrorKind::HeaderPathTypeFailure => "Header has wrong type for path",
|
||||||
&StoreErrorKind::HeaderKeyNotFound => "Header Key not found",
|
&StoreErrorKind::HeaderKeyNotFound => "Header Key not found",
|
||||||
&StoreErrorKind::HeaderTypeFailure => "Header type is wrong",
|
&StoreErrorKind::HeaderTypeFailure => "Header type is wrong",
|
||||||
|
&StoreErrorKind::HookRegisterError => "Hook register error",
|
||||||
|
&StoreErrorKind::HookExecutionError => "Hook execution error",
|
||||||
|
&StoreErrorKind::PreHookExecuteError => "Pre-Hook execution error",
|
||||||
|
&StoreErrorKind::PostHookExecuteError => "Post-Hook execution error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
libimagstore/src/hook/accessor.rs
Normal file
27
libimagstore/src/hook/accessor.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use hook::result::HookResult;
|
||||||
|
use store::FileLockEntry;
|
||||||
|
use storeid::StoreId;
|
||||||
|
|
||||||
|
pub trait StoreIdAccessor : Send + Sync {
|
||||||
|
fn access(&self, &StoreId) -> HookResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MutableHookDataAccessor : Send + Sync {
|
||||||
|
fn access_mut(&self, &mut FileLockEntry) -> HookResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NonMutableHookDataAccessor : Send + Sync {
|
||||||
|
fn access(&self, &FileLockEntry) -> HookResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HookDataAccessor<'a> {
|
||||||
|
StoreIdAccess(&'a StoreIdAccessor),
|
||||||
|
MutableAccess(&'a MutableHookDataAccessor),
|
||||||
|
NonMutableAccess(&'a NonMutableHookDataAccessor),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HookDataAccessorProvider {
|
||||||
|
fn accessor(&self) -> HookDataAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
144
libimagstore/src/hook/aspect.rs
Normal file
144
libimagstore/src/hook/aspect.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
use store::FileLockEntry;
|
||||||
|
use storeid::StoreId;
|
||||||
|
use hook::Hook;
|
||||||
|
use hook::result::HookResult;
|
||||||
|
use hook::accessor::{StoreIdAccessor, MutableHookDataAccessor, NonMutableHookDataAccessor};
|
||||||
|
use hook::accessor::HookDataAccessor as HDA;
|
||||||
|
|
||||||
|
use hook::error::HookError as HE;
|
||||||
|
use hook::error::HookErrorKind as HEK;
|
||||||
|
use configuration::AspectConfig;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Aspect {
|
||||||
|
cfg: Option<AspectConfig>,
|
||||||
|
name: String,
|
||||||
|
hooks: Vec<Box<Hook>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aspect {
|
||||||
|
|
||||||
|
pub fn new(name: String, cfg: Option<AspectConfig>) -> Aspect {
|
||||||
|
Aspect {
|
||||||
|
cfg: cfg,
|
||||||
|
name: name,
|
||||||
|
hooks: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_hook(&mut self, h: Box<Hook>) {
|
||||||
|
self.hooks.push(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoreIdAccessor for Aspect {
|
||||||
|
fn access(&self, id: &StoreId) -> HookResult<()> {
|
||||||
|
use crossbeam;
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
|
||||||
|
if !accessors.iter().all(|a| match a { &HDA::StoreIdAccess(_) => true, _ => false }) {
|
||||||
|
return Err(HE::new(HEK::AccessTypeViolation, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let threads : Vec<HookResult<()>> = accessors
|
||||||
|
.iter()
|
||||||
|
.map(|accessor| {
|
||||||
|
crossbeam::scope(|scope| {
|
||||||
|
scope.spawn(|| {
|
||||||
|
match accessor {
|
||||||
|
&HDA::StoreIdAccess(accessor) => accessor.access(id),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.map_err(|e| ()) // TODO: We're losing the error cause here
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|i| i.join().map_err(|_| HE::new(HEK::HookExecutionError, None)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
threads
|
||||||
|
.into_iter()
|
||||||
|
.fold(Ok(()), |acc, elem| {
|
||||||
|
acc.and_then(|a| {
|
||||||
|
elem.map(|_| a).map_err(|_| HE::new(HEK::HookExecutionError, None))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MutableHookDataAccessor for Aspect {
|
||||||
|
fn access_mut(&self, fle: &mut FileLockEntry) -> HookResult<()> {
|
||||||
|
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
|
||||||
|
|
||||||
|
fn is_file_accessor(a: &HDA) -> bool {
|
||||||
|
match a {
|
||||||
|
&HDA::MutableAccess(_) => true,
|
||||||
|
&HDA::NonMutableAccess(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accessors.iter().all(|a| is_file_accessor(a)) {
|
||||||
|
return Err(HE::new(HEK::AccessTypeViolation, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
for accessor in accessors {
|
||||||
|
match accessor {
|
||||||
|
HDA::MutableAccess(accessor) => try!(accessor.access_mut(fle)),
|
||||||
|
|
||||||
|
// TODO: Naiive implementation.
|
||||||
|
// More sophisticated version would check whether there are _chunks_ of
|
||||||
|
// NonMutableAccess accessors and execute these chunks in parallel. We do not have
|
||||||
|
// performance concerns yet, so this is okay.
|
||||||
|
HDA::NonMutableAccess(accessor) => try!(accessor.access(fle)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonMutableHookDataAccessor for Aspect {
|
||||||
|
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
|
||||||
|
use crossbeam;
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
|
||||||
|
if !accessors.iter().all(|a| match a { &HDA::NonMutableAccess(_) => true, _ => false }) {
|
||||||
|
return Err(HE::new(HEK::AccessTypeViolation, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let threads : Vec<HookResult<()>> = accessors
|
||||||
|
.iter()
|
||||||
|
.map(|accessor| {
|
||||||
|
crossbeam::scope(|scope| {
|
||||||
|
scope.spawn(|| {
|
||||||
|
match accessor {
|
||||||
|
&HDA::NonMutableAccess(accessor) => accessor.access(fle),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.map_err(|e| ()) // TODO: We're losing the error cause here
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|i| i.join().map_err(|_| HE::new(HEK::HookExecutionError, None)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
threads
|
||||||
|
.into_iter()
|
||||||
|
.fold(Ok(()), |acc, elem| {
|
||||||
|
acc.and_then(|a| {
|
||||||
|
elem.map(|_| a).map_err(|_| HE::new(HEK::HookExecutionError, None))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
105
libimagstore/src/hook/error.rs
Normal file
105
libimagstore/src/hook/error.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Error as FmtError;
|
||||||
|
use std::clone::Clone;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::convert::Into;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kind of error
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum HookErrorKind {
|
||||||
|
HookExecutionError,
|
||||||
|
AccessTypeViolation,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoHookError {
|
||||||
|
fn into_hookerror(self) -> HookError;
|
||||||
|
fn into_hookerror_with_cause(self, cause: Box<Error>) -> HookError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<HookError> for HookErrorKind {
|
||||||
|
|
||||||
|
fn into(self) -> HookError {
|
||||||
|
HookError::new(self, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<HookError> for (HookErrorKind, Box<Error>) {
|
||||||
|
|
||||||
|
fn into(self) -> HookError {
|
||||||
|
HookError::new(self.0, Some(self.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hook_error_type_as_str(e: &HookErrorKind) -> &'static str {
|
||||||
|
match e {
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HookErrorKind {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
try!(write!(fmt, "{}", hook_error_type_as_str(self)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error type
|
||||||
|
*/
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HookError {
|
||||||
|
err_type: HookErrorKind,
|
||||||
|
cause: Option<Box<Error>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HookError {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a new HookError from an HookErrorKind, optionally with cause
|
||||||
|
*/
|
||||||
|
pub fn new(errtype: HookErrorKind, cause: Option<Box<Error>>)
|
||||||
|
-> HookError
|
||||||
|
{
|
||||||
|
HookError {
|
||||||
|
err_type: errtype,
|
||||||
|
cause: cause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error type of this HookError
|
||||||
|
*/
|
||||||
|
pub fn err_type(&self) -> HookErrorKind {
|
||||||
|
self.err_type.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HookError {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
try!(write!(fmt, "[{}]", hook_error_type_as_str(&self.err_type.clone())));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for HookError {
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
hook_error_type_as_str(&self.err_type.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
self.cause.as_ref().map(|e| &**e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
20
libimagstore/src/hook/mod.rs
Normal file
20
libimagstore/src/hook/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use toml::Value;
|
||||||
|
|
||||||
|
use self::error::HookError;
|
||||||
|
use store::FileLockEntry;
|
||||||
|
|
||||||
|
pub mod accessor;
|
||||||
|
pub mod aspect;
|
||||||
|
pub mod error;
|
||||||
|
pub mod position;
|
||||||
|
pub mod result;
|
||||||
|
|
||||||
|
use hook::accessor::HookDataAccessorProvider;
|
||||||
|
|
||||||
|
pub trait Hook : HookDataAccessorProvider + Debug + Send + Sync {
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
fn set_config(&mut self, cfg: &Value);
|
||||||
|
}
|
||||||
|
|
13
libimagstore/src/hook/position.rs
Normal file
13
libimagstore/src/hook/position.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HookPosition {
|
||||||
|
PreRead,
|
||||||
|
PostRead,
|
||||||
|
PreCreate,
|
||||||
|
PostCreate,
|
||||||
|
PreRetrieve,
|
||||||
|
PostRetrieve,
|
||||||
|
PreUpdate,
|
||||||
|
PostUpdate,
|
||||||
|
PreDelete,
|
||||||
|
PostDelete,
|
||||||
|
}
|
3
libimagstore/src/hook/result.rs
Normal file
3
libimagstore/src/hook/result.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
use hook::error::HookError;
|
||||||
|
|
||||||
|
pub type HookResult<T> = Result<T, HookError>;
|
|
@ -7,9 +7,12 @@ extern crate regex;
|
||||||
extern crate toml;
|
extern crate toml;
|
||||||
#[cfg(test)] extern crate tempdir;
|
#[cfg(test)] extern crate tempdir;
|
||||||
extern crate semver;
|
extern crate semver;
|
||||||
|
extern crate crossbeam;
|
||||||
|
|
||||||
pub mod storeid;
|
pub mod storeid;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod hook;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
mod configuration;
|
||||||
mod lazyfile;
|
mod lazyfile;
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,30 @@ use std::collections::BTreeMap;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use toml::{Table, Value};
|
use toml::{Table, Value};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use crossbeam;
|
||||||
|
use crossbeam::ScopedJoinHandle;
|
||||||
|
|
||||||
use error::{ParserErrorKind, ParserError};
|
use error::{ParserErrorKind, ParserError};
|
||||||
use error::{StoreError, StoreErrorKind};
|
use error::{StoreError, StoreErrorKind};
|
||||||
use storeid::{StoreId, StoreIdIterator};
|
use storeid::{StoreId, StoreIdIterator};
|
||||||
use lazyfile::LazyFile;
|
use lazyfile::LazyFile;
|
||||||
|
|
||||||
|
use hook::aspect::Aspect;
|
||||||
|
use hook::result::HookResult;
|
||||||
|
use hook::accessor::{ MutableHookDataAccessor,
|
||||||
|
NonMutableHookDataAccessor,
|
||||||
|
StoreIdAccessor,
|
||||||
|
HookDataAccessor,
|
||||||
|
HookDataAccessorProvider};
|
||||||
|
use hook::position::HookPosition;
|
||||||
|
use hook::Hook;
|
||||||
|
|
||||||
/// 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>;
|
||||||
|
|
||||||
|
@ -90,6 +105,26 @@ impl StoreEntry {
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
location: PathBuf,
|
location: PathBuf,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object of the store
|
||||||
|
*/
|
||||||
|
configuration: Option<Value>,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registered hooks
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre_read_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
post_read_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
pre_create_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
post_create_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
pre_retrieve_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
post_retrieve_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
pre_update_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
post_update_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
pre_delete_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
post_delete_aspects : Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal Path->File cache map
|
* Internal Path->File cache map
|
||||||
*
|
*
|
||||||
|
@ -103,8 +138,14 @@ pub struct Store {
|
||||||
impl Store {
|
impl Store {
|
||||||
|
|
||||||
/// Create a new Store object
|
/// Create a new Store object
|
||||||
pub fn new(location: PathBuf) -> Result<Store> {
|
pub fn new(location: PathBuf, store_config: Option<Value>) -> Result<Store> {
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
|
use configuration::*;
|
||||||
|
|
||||||
|
debug!("Validating Store configuration");
|
||||||
|
if !config_is_valid(&store_config) {
|
||||||
|
return Err(StoreError::new(StoreErrorKind::ConfigurationError, None));
|
||||||
|
}
|
||||||
|
|
||||||
debug!("Building new Store object");
|
debug!("Building new Store object");
|
||||||
if !location.exists() {
|
if !location.exists() {
|
||||||
|
@ -122,11 +163,84 @@ impl Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Store building succeeded");
|
let pre_read_aspects = get_pre_read_aspect_names(&store_config)
|
||||||
Ok(Store {
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let post_read_aspects = get_post_read_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let pre_create_aspects = get_pre_create_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let post_create_aspects = get_post_create_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let pre_retrieve_aspects = get_pre_retrieve_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let post_retrieve_aspects = get_post_retrieve_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let pre_update_aspects = get_pre_update_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let post_update_aspects = get_post_update_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let pre_delete_aspects = get_pre_delete_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let post_delete_aspects = get_post_delete_aspect_names(&store_config)
|
||||||
|
.into_iter().map(|n| {
|
||||||
|
let cfg = AspectConfig::get_for(&store_config, n.clone());
|
||||||
|
Aspect::new(n, cfg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let store = Store {
|
||||||
location: location,
|
location: location,
|
||||||
|
configuration: store_config,
|
||||||
|
pre_read_aspects : Arc::new(Mutex::new(pre_read_aspects)),
|
||||||
|
post_read_aspects : Arc::new(Mutex::new(post_read_aspects)),
|
||||||
|
pre_create_aspects : Arc::new(Mutex::new(pre_create_aspects)),
|
||||||
|
post_create_aspects : Arc::new(Mutex::new(post_create_aspects)),
|
||||||
|
pre_retrieve_aspects : Arc::new(Mutex::new(pre_retrieve_aspects)),
|
||||||
|
post_retrieve_aspects : Arc::new(Mutex::new(post_retrieve_aspects)),
|
||||||
|
pre_update_aspects : Arc::new(Mutex::new(pre_update_aspects)),
|
||||||
|
post_update_aspects : Arc::new(Mutex::new(post_update_aspects)),
|
||||||
|
pre_delete_aspects : Arc::new(Mutex::new(pre_delete_aspects)),
|
||||||
|
post_delete_aspects : Arc::new(Mutex::new(post_delete_aspects)),
|
||||||
entries: Arc::new(RwLock::new(HashMap::new())),
|
entries: Arc::new(RwLock::new(HashMap::new())),
|
||||||
})
|
};
|
||||||
|
|
||||||
|
debug!("Store building succeeded");
|
||||||
|
Ok(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storify_id(&self, id: StoreId) -> StoreId {
|
fn storify_id(&self, id: StoreId) -> StoreId {
|
||||||
|
@ -140,6 +254,10 @@ impl Store {
|
||||||
/// Creates the Entry at the given location (inside the entry)
|
/// Creates the Entry at the given location (inside the entry)
|
||||||
pub fn create<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
|
pub fn create<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
|
||||||
let id = self.storify_id(id);
|
let id = self.storify_id(id);
|
||||||
|
if let Err(e) = self.execute_hooks_for_id(self.pre_create_aspects.clone(), &id) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
let hsmap = self.entries.write();
|
let hsmap = self.entries.write();
|
||||||
if hsmap.is_err() {
|
if hsmap.is_err() {
|
||||||
return Err(StoreError::new(StoreErrorKind::LockPoisoned, None))
|
return Err(StoreError::new(StoreErrorKind::LockPoisoned, None))
|
||||||
|
@ -153,13 +271,21 @@ impl Store {
|
||||||
se.status = StoreEntryStatus::Borrowed;
|
se.status = StoreEntryStatus::Borrowed;
|
||||||
se
|
se
|
||||||
});
|
});
|
||||||
Ok(FileLockEntry::new(self, Entry::new(id.clone()), id))
|
|
||||||
|
let mut fle = FileLockEntry::new(self, Entry::new(id.clone()), id);
|
||||||
|
self.execute_hooks_for_mut_file(self.post_create_aspects.clone(), &mut fle)
|
||||||
|
.map_err(|e| StoreError::new(StoreErrorKind::PostHookExecuteError, Some(Box::new(e))))
|
||||||
|
.map(|_| fle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>> {
|
||||||
let id = self.storify_id(id);
|
let id = self.storify_id(id);
|
||||||
|
if let Err(e) = self.execute_hooks_for_id(self.pre_retrieve_aspects.clone(), &id) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
self.entries
|
self.entries
|
||||||
.write()
|
.write()
|
||||||
.map_err(|_| StoreError::new(StoreErrorKind::LockPoisoned, None))
|
.map_err(|_| StoreError::new(StoreErrorKind::LockPoisoned, None))
|
||||||
|
@ -170,6 +296,14 @@ impl Store {
|
||||||
entry
|
entry
|
||||||
})
|
})
|
||||||
.map(|e| FileLockEntry::new(self, e, id))
|
.map(|e| FileLockEntry::new(self, e, id))
|
||||||
|
.and_then(|mut fle| {
|
||||||
|
if let Err(e) = self.execute_hooks_for_mut_file(self.pre_retrieve_aspects.clone(), &mut fle) {
|
||||||
|
Err(StoreError::new(StoreErrorKind::HookExecutionError, Some(Box::new(e))))
|
||||||
|
} else {
|
||||||
|
Ok(fle)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all StoreIds for one module name
|
/// Iterate over all StoreIds for one module name
|
||||||
|
@ -178,8 +312,16 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `FileLockEntry` and write to disk
|
/// Return the `FileLockEntry` and write to disk
|
||||||
pub fn update<'a>(&'a self, entry: FileLockEntry<'a>) -> Result<()> {
|
pub fn update<'a>(&'a self, mut entry: FileLockEntry<'a>) -> Result<()> {
|
||||||
self._update(&entry)
|
if let Err(e) = self.execute_hooks_for_mut_file(self.pre_update_aspects.clone(), &mut entry) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = self._update(&entry) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.execute_hooks_for_mut_file(self.post_update_aspects.clone(), &mut entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal method to write to the filesystem store.
|
/// Internal method to write to the filesystem store.
|
||||||
|
@ -230,7 +372,11 @@ impl Store {
|
||||||
/// Delete an entry
|
/// Delete an entry
|
||||||
pub fn delete(&self, id: StoreId) -> Result<()> {
|
pub fn delete(&self, id: StoreId) -> Result<()> {
|
||||||
let id = self.storify_id(id);
|
let id = self.storify_id(id);
|
||||||
let mut entries_lock = self.entries.write();
|
if let Err(e) = self.execute_hooks_for_id(self.pre_delete_aspects.clone(), &id) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries_lock = self.entries.write();
|
||||||
if entries_lock.is_err() {
|
if entries_lock.is_err() {
|
||||||
return Err(StoreError::new(StoreErrorKind::LockPoisoned, None))
|
return Err(StoreError::new(StoreErrorKind::LockPoisoned, None))
|
||||||
}
|
}
|
||||||
|
@ -244,7 +390,11 @@ impl Store {
|
||||||
|
|
||||||
// remove the entry first, then the file
|
// remove the entry first, then the file
|
||||||
entries.remove(&id);
|
entries.remove(&id);
|
||||||
remove_file(&id).map_err(|e| StoreError::new(StoreErrorKind::FileError, Some(Box::new(e))))
|
if let Err(e) = remove_file(&id) {
|
||||||
|
return Err(StoreError::new(StoreErrorKind::FileError, Some(Box::new(e))));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.execute_hooks_for_id(self.post_delete_aspects.clone(), &id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the path where this store is on the disk
|
/// Gets the path where this store is on the disk
|
||||||
|
@ -252,6 +402,113 @@ impl Store {
|
||||||
&self.location
|
&self.location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_hook(&mut self,
|
||||||
|
position: HookPosition,
|
||||||
|
aspect_name: &String,
|
||||||
|
mut h: Box<Hook>)
|
||||||
|
-> Result<()>
|
||||||
|
{
|
||||||
|
debug!("Registering hook: {:?}", h);
|
||||||
|
debug!(" in position: {:?}", position);
|
||||||
|
debug!(" with aspect: {:?}", aspect_name);
|
||||||
|
|
||||||
|
let mut guard = match position {
|
||||||
|
HookPosition::PreRead => self.pre_read_aspects.clone(),
|
||||||
|
HookPosition::PostRead => self.post_read_aspects.clone(),
|
||||||
|
HookPosition::PreCreate => self.pre_create_aspects.clone(),
|
||||||
|
HookPosition::PostCreate => self.post_create_aspects.clone(),
|
||||||
|
HookPosition::PreRetrieve => self.pre_retrieve_aspects.clone(),
|
||||||
|
HookPosition::PostRetrieve => self.post_retrieve_aspects.clone(),
|
||||||
|
HookPosition::PreUpdate => self.pre_update_aspects.clone(),
|
||||||
|
HookPosition::PostUpdate => self.post_update_aspects.clone(),
|
||||||
|
HookPosition::PreDelete => self.pre_delete_aspects.clone(),
|
||||||
|
HookPosition::PostDelete => self.post_delete_aspects.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut guard = guard
|
||||||
|
.deref()
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| StoreError::new(StoreErrorKind::HookRegisterError, None));
|
||||||
|
|
||||||
|
if guard.is_err() {
|
||||||
|
return Err(StoreError::new(StoreErrorKind::HookRegisterError,
|
||||||
|
Some(Box::new(guard.err().unwrap()))));
|
||||||
|
}
|
||||||
|
let mut guard = guard.unwrap();
|
||||||
|
for mut aspect in guard.deref_mut() {
|
||||||
|
if aspect.name().clone() == aspect_name.clone() {
|
||||||
|
self.get_config_for_hook(h.name()).map(|config| h.set_config(config));
|
||||||
|
aspect.register_hook(h);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(StoreError::new(StoreErrorKind::HookRegisterError, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_for_hook(&self, name: &str) -> Option<&Value> {
|
||||||
|
match &self.configuration {
|
||||||
|
&Some(Value::Table(ref tabl)) => {
|
||||||
|
tabl.get("hooks")
|
||||||
|
.map(|hook_section| {
|
||||||
|
match hook_section {
|
||||||
|
&Value::Table(ref tabl) => tabl.get(name),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(None)
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_hooks_for_id(&self,
|
||||||
|
aspects: Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
id: &StoreId)
|
||||||
|
-> Result<()>
|
||||||
|
{
|
||||||
|
let guard = aspects.deref().lock();
|
||||||
|
if guard.is_err() { return Err(StoreError::new(StoreErrorKind::PreHookExecuteError, None)) }
|
||||||
|
|
||||||
|
guard.unwrap().deref().iter()
|
||||||
|
.fold(Ok(()), |acc, aspect| {
|
||||||
|
debug!("[Aspect][exec]: {:?}", aspect);
|
||||||
|
acc.and_then(|_| (aspect as &StoreIdAccessor).access(id))
|
||||||
|
})
|
||||||
|
.map_err(|e| StoreError::new(StoreErrorKind::PreHookExecuteError, Some(Box::new(e))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_hooks_for_mut_file(&self,
|
||||||
|
aspects: Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
fle: &mut FileLockEntry)
|
||||||
|
-> Result<()>
|
||||||
|
{
|
||||||
|
let guard = aspects.deref().lock();
|
||||||
|
if guard.is_err() { return Err(StoreError::new(StoreErrorKind::PreHookExecuteError, None)) }
|
||||||
|
|
||||||
|
guard.unwrap().deref().iter()
|
||||||
|
.fold(Ok(()), |acc, aspect| {
|
||||||
|
debug!("[Aspect][exec]: {:?}", aspect);
|
||||||
|
acc.and_then(|_| aspect.access_mut(fle))
|
||||||
|
})
|
||||||
|
.map_err(|e| StoreError::new(StoreErrorKind::PreHookExecuteError, Some(Box::new(e))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_hooks_for_file(&self,
|
||||||
|
aspects: Arc<Mutex<Vec<Aspect>>>,
|
||||||
|
fle: &FileLockEntry)
|
||||||
|
-> Result<()>
|
||||||
|
{
|
||||||
|
let guard = aspects.deref().lock();
|
||||||
|
if guard.is_err() { return Err(StoreError::new(StoreErrorKind::PreHookExecuteError, None)) }
|
||||||
|
|
||||||
|
guard.unwrap().deref().iter()
|
||||||
|
.fold(Ok(()), |acc, aspect| {
|
||||||
|
debug!("[Aspect][exec]: {:?}", aspect);
|
||||||
|
acc.and_then(|_| (aspect as &NonMutableHookDataAccessor).access(fle))
|
||||||
|
})
|
||||||
|
.map_err(|e| StoreError::new(StoreErrorKind::PreHookExecuteError, Some(Box::new(e))))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Store {
|
impl Drop for Store {
|
||||||
|
|
Loading…
Reference in a new issue