diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index d245feb3..e90750ad 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -190,14 +190,14 @@ impl StoreEntry { pub struct Store { location: PathBuf, - /** - * Configuration object of the store - */ + /// + /// Configuration object of the store + /// configuration: Option, - /* - * Registered hooks - */ + // + // Registered hooks + // store_unload_aspects : Arc>>, @@ -212,19 +212,42 @@ pub struct Store { pre_move_aspects : Arc>>, post_move_aspects : Arc>>, - /** - * Internal Path->File cache map - * - * Caches the files, so they remain flock()ed - * - * Could be optimized for a threadsafe HashMap - */ + /// + /// Internal Path->File cache map + /// + /// Caches the files, so they remain flock()ed + /// + /// Could be optimized for a threadsafe HashMap + /// entries: Arc>>, } impl Store { /// Create a new Store object + /// + /// This opens a Store in `location` using the configuration from `store_config` (if absent, it + /// uses defaults). + /// + /// If the configuration is not valid, this fails. + /// + /// If the location does not exist, creating directories is by default denied and the operation + /// fails, if not configured otherwise. + /// An error is returned in this case. + /// + /// If the path exists and is a file, the operation is aborted as well, an error is returned. + /// + /// After that, the store hook aspects are created and registered in the store. + /// + /// # Return values + /// + /// - On success: Store object + /// - On Failure: + /// - ConfigurationError if config is faulty + /// - IoError(FileError(CreateStoreDirDenied())) if store location does not exist and creating + /// is denied + /// - StorePathCreate(_) if creating the store directory failed + /// - StorePathExists() if location exists but is a file pub fn new(location: PathBuf, store_config: Option) -> Result { use configuration::*; @@ -399,6 +422,25 @@ impl Store { } /// Creates the Entry at the given location (inside the entry) + /// + /// # Executed Hooks + /// + /// - Pre create aspects + /// - post create aspects + /// + /// # Return value + /// + /// On success: FileLockEntry + /// + /// On error: + /// - Errors StoreId::into_storeid() might return + /// - CreateCallError(HookExecutionError(PreHookExecuteError(_))) + /// of the first failing pre hook. + /// - CreateCallError(HookExecutionError(PostHookExecuteError(_))) + /// of the first failing post hook. + /// - CreateCallError(LockPoisoned()) if the internal lock is poisened. + /// - CreateCallError(EntryAlreadyExists()) if the entry exists already. + /// pub fn create<'a, S: IntoStoreId>(&'a self, id: S) -> Result> { let id = try!(id.into_storeid()).with_base(self.path().clone()); if let Err(e) = self.execute_hooks_for_id(self.pre_create_aspects.clone(), &id) { @@ -437,6 +479,24 @@ impl Store { /// /// Implicitely creates a entry in the store if there is no entry with the id `id`. For a /// non-implicitely-create look at `Store::get`. + /// + /// # Executed Hooks + /// + /// - Pre retrieve aspects + /// - post retrieve aspects + /// + /// # Return value + /// + /// On success: FileLockEntry + /// + /// On error: + /// - Errors StoreId::into_storeid() might return + /// - RetrieveCallError(HookExecutionError(PreHookExecuteError(_))) + /// of the first failing pre hook. + /// - RetrieveCallError(HookExecutionError(PostHookExecuteError(_))) + /// of the first failing post hook. + /// - RetrieveCallError(LockPoisoned()) if the internal lock is poisened. + /// pub fn retrieve<'a, S: IntoStoreId>(&'a self, id: S) -> Result> { let id = try!(id.into_storeid()).with_base(self.path().clone()); if let Err(e) = self.execute_hooks_for_id(self.pre_retrieve_aspects.clone(), &id) { @@ -470,7 +530,19 @@ impl Store { /// Get an entry from the store if it exists. /// - /// This executes the {pre,post}_retrieve_aspects hooks. + /// # Executed Hooks + /// + /// - Pre get aspects + /// - post get aspects + /// + /// # Return value + /// + /// On success: Some(FileLockEntry) or None + /// + /// On error: + /// - Errors StoreId::into_storeid() might return + /// - Errors Store::retrieve() might return + /// pub fn get<'a, S: IntoStoreId + Clone>(&'a self, id: S) -> Result>> { let id = try!(id.into_storeid()).with_base(self.path().clone()); @@ -490,6 +562,16 @@ impl Store { } /// Iterate over all StoreIds for one module name + /// + /// # Returns + /// + /// On success: An iterator over all entries in the module + /// + /// On failure: + /// - RetrieveForModuleCallError(GlobError(EncodingError())) if the path string cannot be + /// encoded + /// - GRetrieveForModuleCallError(GlobError(lobError())) if the glob() failed. + /// pub fn retrieve_for_module(&self, mod_name: &str) -> Result { let mut path = self.path().clone(); path.push(mod_name); @@ -506,12 +588,18 @@ impl Store { .map_err_into(SEK::RetrieveForModuleCallError) } - // Walk the store tree for the module + /// Walk the store tree for the module + /// + /// The difference between a `Walk` and a `StoreIdIterator` is that with a `Walk`, one can find + /// "collections" (folders). pub fn walk<'a>(&'a self, mod_name: &str) -> Walk { Walk::new(self.path().clone(), mod_name) } /// Return the `FileLockEntry` and write to disk + /// + /// See `Store::_update()`. + /// pub fn update<'a>(&'a self, mut entry: FileLockEntry<'a>) -> Result<()> { self._update(&mut entry, false).map_err_into(SEK::UpdateCallError) } @@ -519,8 +607,29 @@ impl Store { /// Internal method to write to the filesystem store. /// /// # Assumptions + /// /// This method assumes that entry is dropped _right after_ the call, hence /// it is not public. + /// + /// # Executed Hooks + /// + /// - Pre update aspects + /// - post update aspects + /// + /// # Return value + /// + /// On success: Entry + /// + /// On error: + /// - UpdateCallError(HookExecutionError(PreHookExecuteError(_))) + /// of the first failing pre hook. + /// - UpdateCallError(HookExecutionError(PostHookExecuteError(_))) + /// of the first failing post hook. + /// - UpdateCallError(LockPoisoned()) if the internal write lock cannot be aquierd. + /// - IdNotFound() if the entry was not found in the stor + /// - Errors Entry::verify() might return + /// - Errors StoreEntry::write_entry() might return + /// fn _update<'a>(&'a self, mut entry: &mut FileLockEntry<'a>, modify_presence: bool) -> Result<()> { let _ = try!(self.execute_hooks_for_mut_file(self.pre_update_aspects.clone(), &mut entry) .map_err_into(SEK::PreHookExecuteError) @@ -546,7 +655,6 @@ impl Store { se.status = StoreEntryStatus::Present; } - self.execute_hooks_for_mut_file(self.post_update_aspects.clone(), &mut entry) .map_err_into(SEK::PostHookExecuteError) .map_err_into(SEK::HookExecutionError) @@ -555,6 +663,22 @@ impl Store { /// Retrieve a copy of a given entry, this cannot be used to mutate /// the one on disk + /// + /// TODO: Create Hooks for retrieving a copy + /// + /// # Executed Hooks + /// + /// - (none yet) + /// + /// # Return value + /// + /// On success: Entry + /// + /// On error: + /// - RetrieveCopyCallError(LockPoisoned()) if the internal write lock cannot be aquierd. + /// - RetrieveCopyCallError(IdLocked()) if the Entry is borrowed currently + /// - Errors StoreEntry::new() might return + /// pub fn retrieve_copy(&self, id: S) -> Result { let id = try!(id.into_storeid()).with_base(self.path().clone()); let entries = match self.entries.write() { @@ -574,6 +698,25 @@ impl Store { } /// Delete an entry + /// + /// # Executed Hooks + /// + /// - Pre delete aspects, if the id can be used + /// - Post delete aspects, if the operation succeeded + /// + /// # Return value + /// + /// On success: () + /// + /// On error: + /// - DeleteCallError(HookExecutionError(PreHookExecuteError(_))) + /// of the first failing pre hook. + /// - DeleteCallError(HookExecutionError(PostHookExecuteError(_))) + /// of the first failing post hook. + /// - DeleteCallError(LockPoisoned()) if the internal write lock cannot be aquierd. + /// - DeleteCallError(FileNotFound()) if the StoreId refers to a non-existing entry. + /// - DeleteCallError(FileError()) if the internals failed to remove the file. + /// pub fn delete(&self, id: S) -> Result<()> { let id = try!(id.into_storeid()).with_base(self.path().clone()); if let Err(e) = self.execute_hooks_for_id(self.pre_delete_aspects.clone(), &id) { @@ -617,6 +760,8 @@ impl Store { /// Save a copy of the Entry in another place /// Executes the post_move_aspects for the new id + /// + /// TODO: Introduce new aspect for `save_to()`. pub fn save_to(&self, entry: &FileLockEntry, new_id: StoreId) -> Result<()> { self.save_to_other_location(entry, new_id, false) } @@ -624,6 +769,8 @@ impl Store { /// Save an Entry in another place /// Removes the original entry /// Executes the post_move_aspects for the new id + /// + /// TODO: Introduce new aspect for `save_as()`. pub fn save_as(&self, entry: FileLockEntry, new_id: StoreId) -> Result<()> { self.save_to_other_location(&entry, new_id, true) } @@ -758,6 +905,21 @@ impl Store { &self.location } + /// Register a hook in the store. + /// + /// A hook is registered by a position (when should the hook be executed) and an aspect name. + /// The aspect name must be in the configuration file, so the configuration for the hook can be + /// passed to the `Hook` object. + /// + /// # Available Hook positions + /// + /// The hook positions are described in the type description of `HookPosition`. + /// + /// # Aspect names + /// + /// Aspect names are arbitrary, though sane things like "debug" or "vcs" are encouraged. + /// Refer to the documentation for more information. + /// pub fn register_hook(&mut self, position: HookPosition, aspect_name: &str, @@ -800,6 +962,7 @@ impl Store { Err(SEK::HookRegisterError.into_error_with_cause(Box::new(annfe))) } + /// Get the configuration for a hook by the name of the hook, from the configuration file. fn get_config_for_hook(&self, name: &str) -> Option<&Value> { match self.configuration { Some(Value::Table(ref tabl)) => { @@ -819,6 +982,13 @@ impl Store { } } + /// Execute all hooks from all aspects for a Store Id object. + /// + /// # Return value + /// + /// - () on success + /// - Error on the first failing hook. + /// fn execute_hooks_for_id(&self, aspects: Arc>>, id: &StoreId) @@ -834,6 +1004,13 @@ impl Store { .map_err(|e| HookErrorKind::HookExecutionError.into_error_with_cause(e)) } + /// Execute all hooks from all aspects for a mutable `FileLockEntry` object. + /// + /// # Return value + /// + /// - () on success + /// - Error on the first failing hook. + /// fn execute_hooks_for_mut_file(&self, aspects: Arc>>, fle: &mut FileLockEntry) @@ -853,6 +1030,7 @@ impl Store { impl Debug for Store { + /// TODO: Make pretty. fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FMTError> { try!(write!(fmt, " --- Store ---\n")); try!(write!(fmt, "\n")); @@ -877,11 +1055,12 @@ impl Debug for Store { impl Drop for Store { - /** - * Unlock all files on drop - * - * TODO: Unlock them - */ + /// + /// Unlock all files on drop + // + /// TODO: Unlock them + /// TODO: Resolve this dirty hack with the StoreId for the Store drop hooks. + /// fn drop(&mut self) { match StoreId::new(Some(self.location.clone()), PathBuf::from(".")) { Err(e) => { @@ -909,6 +1088,10 @@ pub struct FileLockEntry<'a> { } impl<'a> FileLockEntry<'a, > { + + /// Create a new FileLockEntry based on a `Entry` object. + /// + /// Only for internal use. fn new(store: &'a Store, entry: Entry) -> FileLockEntry<'a> { FileLockEntry { store: store, @@ -940,7 +1123,11 @@ impl<'a> DerefMut for FileLockEntry<'a> { #[cfg(not(test))] impl<'a> Drop for FileLockEntry<'a> { + /// This will silently ignore errors, use `Store::update` if you want to catch the errors + /// + /// This might panic if the store was compiled with the early-panic feature (which is not + /// intended for production use, though). fn drop(&mut self) { use libimagerror::trace::trace_error_dbg; @@ -956,21 +1143,21 @@ impl<'a> Drop for FileLockEntry<'a> { #[cfg(test)] impl<'a> Drop for FileLockEntry<'a> { + /// This will not silently ignore errors but prints the result of the _update() call for testing fn drop(&mut self) { let _ = self.store._update(self, true).map_err(|e| trace_error(&e)); } + } /// `EntryContent` type pub type EntryContent = String; -/** - * An Entry of the store - * - * Contains location, header and content part. - */ +/// An Entry of the store +// +/// Contains location, header and content part. #[derive(Debug, Clone)] pub struct Entry { location: StoreId, @@ -980,6 +1167,10 @@ pub struct Entry { impl Entry { + /// Create a new store entry with its location at `loc`. + /// + /// This creates the entry with the default header from `Entry::default_header()` and an empty + /// content. pub fn new(loc: StoreId) -> Entry { Entry { location: loc, @@ -988,10 +1179,16 @@ impl Entry { } } + /// Get the default Header for an Entry. + /// + /// This function should be used to get a new Header, as the default header may change. Via + /// this function, compatibility is ensured. pub fn default_header() -> Value { // BTreeMap Value::default_header() } + /// See `Entry::from_str()`, as this function is used internally. This is just a wrapper for + /// convenience. pub fn from_reader(loc: S, file: &mut Read) -> Result { let text = { let mut s = String::new(); @@ -1001,6 +1198,18 @@ impl Entry { Self::from_str(loc, &text[..]) } + /// Create a new Entry, with contents from the string passed. + /// + /// The passed string _must_ be a complete valid store entry, including header. So this is + /// probably not what end-users want to call. + /// + /// # Return value + /// + /// This errors if + /// + /// - String cannot be matched on regex to find header and content + /// - Header cannot be parsed into a TOML object + /// pub fn from_str(loc: S, s: &str) -> Result { debug!("Building entry from string"); lazy_static! { @@ -1032,32 +1241,44 @@ impl Entry { }) } + /// Return the string representation of this entry + /// + /// This means not only the content of the entry, but the complete entry (from memory, not from + /// disk). pub fn to_str(&self) -> String { format!("---\n{header}---\n{content}", header = ::toml::encode_str(&self.header), content = self.content) } + /// Get the location of the Entry pub fn get_location(&self) -> &StoreId { &self.location } + /// Get the header of the Entry pub fn get_header(&self) -> &Value { &self.header } + /// Get the header mutably of the Entry pub fn get_header_mut(&mut self) -> &mut Value { &mut self.header } + /// Get the content of the Entry pub fn get_content(&self) -> &EntryContent { &self.content } + /// Get the content mutably of the Entry pub fn get_content_mut(&mut self) -> &mut EntryContent { &mut self.content } + /// Verify the entry. + /// + /// Currently, this only verifies the header. This might change in the future. pub fn verify(&self) -> Result<()> { self.header.verify() }