diff options
Diffstat (limited to 'libimagruby/src/store.rs')
-rw-r--r-- | libimagruby/src/store.rs | 559 |
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 +} + |