summaryrefslogtreecommitdiffstats
path: root/libimagruby/src/store.rs
diff options
context:
space:
mode:
Diffstat (limited to 'libimagruby/src/store.rs')
-rw-r--r--libimagruby/src/store.rs559
1 files changed, 559 insertions, 0 deletions
diff --git a/libimagruby/src/store.rs b/libimagruby/src/store.rs
new file mode 100644
index 00000000..4c3a49a2
--- /dev/null
+++ b/libimagruby/src/store.rs
@@ -0,0 +1,559 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use libimagstore::store::Store;
+use libimagerror::trace::trace_error;
+use std::error::Error;
+use std::ops::Deref;
+use std::ops::DerefMut;
+
+use ruru::{Class, Object, AnyObject, Boolean, RString, VM, Hash, NilClass, VerifiedObject};
+
+use ruby_utils::IntoToml;
+use toml_utils::IntoRuby;
+use util::Wrap;
+use util::Unwrap;
+
+use storeid::RStoreId;
+use entry::RFileLockEntryHandle;
+use cache::StoreHandle;
+
+wrappable_struct!(StoreHandle, StoreWrapper, STORE_WRAPPER);
+class!(RStoreHandle);
+impl_wrap!(StoreHandle => STORE_WRAPPER);
+impl_unwrap!(RStoreHandle => StoreHandle => STORE_WRAPPER);
+impl_verified_object!(RStoreHandle);
+
+macro_rules! call_on_store_by_handle {
+ {
+ $store_handle:ident named $name:ident inside $operation:block
+ }=> {{
+ call_on_store_by_handle! {
+ $store_handle
+ named $name
+ inside $operation
+ on fail return NilClass::new().to_any_object()
+ }
+ }};
+
+ {
+ $store_handle:ident named $name:ident inside $operation:block on fail return $ex:expr
+ } => {{
+ use cache::RUBY_STORE_CACHE;
+
+ let arc = RUBY_STORE_CACHE.clone();
+ {
+ let lock = arc.lock();
+ match lock {
+ Ok(mut hm) => {
+ match hm.get($store_handle) {
+ Some($name) => { $operation },
+ None => {
+ VM::raise(Class::from_existing("RuntimeError"),
+ "Tried to operate on non-existing object");
+ $ex
+ }
+ }
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ $ex
+ }
+ }
+ }
+ }};
+}
+
+macro_rules! call_on_store {
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ $fle_name:ident <- fetch $fle_handle_name:ident
+ operation $operation:block
+ } => {
+ call_on_store! {
+ $store_name <- $itself wrapped inside $wrapper,
+ $fle_name <- fetch $fle_handle_name,
+ operation $operation,
+ on fail return NilClass::new()
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ $fle_name:ident <- fetch $fle_handle_name:ident,
+ operation $operation:block,
+ on fail return $fail_expr:expr
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! {
+ handle named $store_name inside {
+ let $fle_name = match $store_name.get($fle_handle_name) {
+ Ok(Some(fle)) => fle,
+ Ok(None) => {
+ VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
+ return $fail_expr
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return $fail_expr
+ },
+ };
+ $operation
+ }
+ on fail return $fail_expr
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ operation $operation:block,
+ on fail return $fail_expr:expr
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! {
+ handle named $store_name inside $operation on fail return $fail_expr
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ operation $block
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! { handle named $name inside $operation }
+ };
+}
+
+methods!(
+ RStoreHandle,
+ itself,
+
+ // Build a new Store object, return a handle to it.
+ //
+ // This function takes a boolean whether the store should include debugging functionality
+ // (namingly the debug hooks) and a runtimepath, where the store lifes.
+ // It then builds a Store object (raising errors on failure and returning Nil) and a handle for
+ // it.
+ // It puts the store object and the handle in the cache and returns the handle as object to the
+ // Ruby code.
+ //
+ // # Returns
+ //
+ // Nil on failure (including raising an error)
+ // StoreHandle on success
+ //
+ fn new(store_debugging: Boolean, rtp: RString) -> AnyObject {
+ use std::path::PathBuf;
+ use libimagerror::into::IntoError;
+ use libimagerror::trace::trace_error;
+ use libimagerror::trace::trace_error_dbg;
+ use libimagerror::trace::trace_error_exit;
+ use libimagrt::configuration::ConfigErrorKind;
+ use libimagrt::configuration::Configuration;
+ use libimagrt::error::RuntimeErrorKind;
+ use libimagstore::error::StoreErrorKind;
+ use libimagstore::hook::Hook;
+ use libimagstore::hook::position::HookPosition as HP;
+ use libimagstorestdhook::debug::DebugHook;
+ use libimagstorestdhook::vcs::git::delete::DeleteHook as GitDeleteHook;
+ use libimagstorestdhook::vcs::git::store_unload::StoreUnloadHook as GitStoreUnloadHook;
+ use libimagstorestdhook::vcs::git::update::UpdateHook as GitUpdateHook;
+
+ use cache::RUBY_STORE_CACHE;
+
+ let store_debugging = typecheck!(store_debugging or return any NilClass::new()).to_bool();
+ let rtp = PathBuf::from(typecheck!(rtp or return any NilClass::new()).to_string());
+
+ if !rtp.exists() || !rtp.is_dir() {
+ VM::raise(Class::from_existing("RuntimeError"), "Runtimepath not a directory");
+ return NilClass::new().to_any_object();
+ }
+
+ let store_config = match Configuration::new(&rtp) {
+ Ok(mut cfg) => cfg.store_config().cloned(),
+ Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ } else {
+ warn!("No config file found.");
+ warn!("Continuing without configuration file");
+ None
+ },
+ };
+
+ let storepath = {
+ let mut spath = rtp.clone();
+ spath.push("store");
+ spath
+ };
+
+ let store = Store::new(storepath.clone(), store_config).map(|mut store| {
+ // If we are debugging, generate hooks for all positions
+ if store_debugging {
+ let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
+ (Box::new(DebugHook::new(HP::PreCreate)) , "debug", HP::PreCreate),
+ (Box::new(DebugHook::new(HP::PostCreate)) , "debug", HP::PostCreate),
+ (Box::new(DebugHook::new(HP::PreRetrieve)) , "debug", HP::PreRetrieve),
+ (Box::new(DebugHook::new(HP::PostRetrieve)) , "debug", HP::PostRetrieve),
+ (Box::new(DebugHook::new(HP::PreUpdate)) , "debug", HP::PreUpdate),
+ (Box::new(DebugHook::new(HP::PostUpdate)) , "debug", HP::PostUpdate),
+ (Box::new(DebugHook::new(HP::PreDelete)) , "debug", HP::PreDelete),
+ (Box::new(DebugHook::new(HP::PostDelete)) , "debug", HP::PostDelete),
+ ];
+
+ // If hook registration fails, trace the error and warn, but continue.
+ for (hook, aspectname, position) in hooks {
+ if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
+ if e.err_type() == StoreErrorKind::HookRegisterError {
+ trace_error_dbg(&e);
+ warn!("Registering debug hook with store failed");
+ } else {
+ trace_error(&e);
+ };
+ }
+ }
+ }
+
+ let sp = storepath;
+
+ let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
+ (Box::new(GitDeleteHook::new(sp.clone(), HP::PostDelete)), "vcs", HP::PostDelete),
+ (Box::new(GitUpdateHook::new(sp.clone(), HP::PostUpdate)), "vcs", HP::PostUpdate),
+ (Box::new(GitStoreUnloadHook::new(sp)), "vcs", HP::StoreUnload),
+ ];
+
+ for (hook, aspectname, position) in hooks {
+ if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
+ if e.err_type() == StoreErrorKind::HookRegisterError {
+ trace_error_dbg(&e);
+ warn!("Registering git hook with store failed");
+ } else {
+ trace_error(&e);
+ };
+ }
+ }
+
+ store
+ });
+
+ let store = match store {
+ Ok(s) => s,
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ },
+ };
+
+ let store_handle = StoreHandle::new();
+
+ let arc = RUBY_STORE_CACHE.clone();
+ {
+ let lock = arc.lock();
+ match lock {
+ Ok(mut hm) => {
+ hm.insert(store_handle.clone(), store);
+ return store_handle.wrap().to_any_object();
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ }
+ }
+ }
+
+ }
+
+
+ // Create an FileLockEntry in the store
+ //
+ // # Returns:
+ //
+ // On success: A RFileLockEntry
+ // On failure: Nil
+ // On error: Nil + Exception
+ //
+ fn create(id: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.create(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(entry) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Retrieve an FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success: A RFileLockEntry
+ // On error: Nil + Exception
+ //
+ fn retrieve(id: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.retrieve(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(entry) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Get an FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success, if there is some: A RFileLockEntry
+ // On success, if there is none: Nil
+ // On error: Nil + Exception
+ //
+ fn get(sid: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(sid or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.get(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(None) => NilClass::new().to_any_object(),
+ Ok(Some(entry)) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Get all FileLockEntry of a module from the store
+ //
+ // # Returns:
+ //
+ // On success: A Array[RFileLockEntry]
+ // On error: Nil + Exception
+ //
+ fn retrieve_for_module(name: RString) -> AnyObject {
+ unimplemented!()
+ }
+
+ // Update a FileLockEntry in the store
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn update(fle: RFileLockEntryHandle) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.update(real_fle) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Delete a FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn delete(sid: RStoreId) -> NilClass {
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ if let Err(e) = store.delete(sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Save a FileLockEntry in a new path inside the store, keep the RFileLockEntry
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn save_to(fle: RFileLockEntryHandle, sid: RStoreId) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.save_to(&real_fle, sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Save a FileLockEntry in a new path inside the store, move the RFileLockEntry
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn save_as(fle: RFileLockEntryHandle, sid: RStoreId) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.save_as(real_fle, sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Move one entry in the store to another place, by its ID
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn move_by_id(old: RStoreId, nw: RStoreId) -> NilClass {
+ let old = typecheck!(old).unwrap().clone();
+ let nw = typecheck!(nw).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ if let Err(e) = store.move_by_id(old, nw) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Get the path of the store object
+ //
+ // # Returns:
+ //
+ // A RString
+ //
+ fn path() -> RString {
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ store.path()
+ .clone()
+ .to_str()
+ .map(RString::new)
+ .unwrap_or(RString::new(""))
+ },
+ on fail return RString::new("")
+ }
+ }
+
+);
+
+pub fn setup() -> Class {
+ let mut class = Class::new("RStoreHandle", None);
+ class.define(|itself| {
+ itself.def_self("new" , new);
+ itself.def("create" , create);
+ itself.def("retrieve" , retrieve);
+ itself.def("get" , get);
+ itself.def("retrieve_for_module" , retrieve_for_module);
+ itself.def("update" , update);
+ itself.def("delete" , delete);
+ itself.def("save_to" , save_to);
+ itself.def("save_as" , save_as);
+ itself.def("move_by_id" , move_by_id);
+ itself.def("path" , path);
+ });
+ class
+}
+