// // imag - the personal information management suite for the commandline // Copyright (C) 2015-2019 Matthias Beyer 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 toml_query::insert::TomlValueInsertExt; use toml_query::read::TomlValueReadTypeExt; use toml::Value; use libimagstore::store::Store; use libimagstore::store::FileLockEntry; use libimagstore::storeid::StoreId; use libimagentryutil::isa::Is; use libimagerror::errors::ErrorMsg as EM; use failure::Fallible as Result; use failure::ResultExt; use failure::Error; use failure::err_msg; use crate::iter::CategoryNameIter; use crate::category::IsCategory; pub const CATEGORY_REGISTER_NAME_FIELD_PATH : &'static str = "category.register.name"; /// Extension on the Store to make it a register for categories /// /// The register writes files to the pub trait CategoryStore { fn category_exists(&self, name: &str) -> Result; fn create_category<'a>(&'a self, name: &str) -> Result>; fn delete_category(&self, name: &str) -> Result<()>; fn all_category_names(&self) -> Result; fn get_category_by_name(&self, name: &str) -> Result>; } impl CategoryStore for Store { /// Check whether a category exists fn category_exists(&self, name: &str) -> Result { trace!("Category exists? '{}'", name); let sid = mk_category_storeid(name)?; represents_category(self, sid, name) } /// Create a category /// /// Fails if the category already exists (returns false then) fn create_category<'a>(&'a self, name: &str) -> Result> { trace!("Creating category: '{}'", name); let sid = mk_category_storeid(name)?; let mut entry = self.create(sid)?; entry.set_isflag::()?; let _ = entry .get_header_mut() .insert(CATEGORY_REGISTER_NAME_FIELD_PATH, Value::String(String::from(name)))?; trace!("Creating category worked: '{}'", name); Ok(entry) } /// Delete a category /// /// Automatically removes all category settings from entries which are linked to this category. fn delete_category(&self, name: &str) -> Result<()> { use libimagentrylink::linkable::Linkable; use crate::category::Category; trace!("Deleting category: '{}'", name); let sid = mk_category_storeid(name)?; { let mut category = self.get(sid.clone())? .ok_or_else(|| Error::from(err_msg("Category does not exist"))) .map_err(Error::from)?; for entry in category.get_entries(self)? { let mut entry = entry?; let _ = category.remove_link(&mut entry)?; } } self.delete(sid) } /// Get all category names fn all_category_names(&self) -> Result { trace!("Getting all category names"); Ok(CategoryNameIter::new(self, self.entries()?.into_storeid_iter())) } /// Get a category by its name /// /// Returns the FileLockEntry which represents the category, so one can link to it and use it /// like a normal file in the store (which is exactly what it is). fn get_category_by_name(&self, name: &str) -> Result> { trace!("Getting category by name: '{}'", name); let sid = mk_category_storeid(name)?; self.get(sid) .context(err_msg("Store write error")) .map_err(Error::from) } } #[cfg(test)] mod tests { extern crate env_logger; use std::path::PathBuf; use super::*; use libimagstore::store::Store; pub fn get_store() -> Store { Store::new_inmemory(PathBuf::from("/"), &None).unwrap() } #[test] fn test_non_existing_category_exists() { let exists = get_store().category_exists("nonexistent"); assert!(exists.is_ok(), format!("Expected Ok(_), got: {:?}", exists)); let exists = exists.unwrap(); assert!(!exists); } #[test] fn test_creating_category() { let category_name = "examplecategory"; let store = get_store(); let res = store.create_category(category_name); assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); } #[test] fn test_creating_category_creates_store_entry() { let category_name = "examplecategory"; let store = get_store(); { let res = store.create_category(category_name); assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); } let category = store.get(PathBuf::from(format!("category/{}", category_name))); assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category)); let category = category.unwrap(); assert!(category.is_some()); } #[test] fn test_creating_category_creates_store_entry_with_header_field_set() { let _ = env_logger::try_init(); let category_name = "examplecategory"; let store = get_store(); { let res = store.create_category(category_name); assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); } let id = PathBuf::from(format!("category/{}", category_name)); debug!("Trying: {:?}", id); let category = store.get(id); assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category)); let category = category.unwrap(); assert!(category.is_some()); let category = category.unwrap(); let header_field = category.get_header().read_string(CATEGORY_REGISTER_NAME_FIELD_PATH); assert!(header_field.is_ok(), format!("Expected Ok(_), got: {:?}", header_field)); let header_field = header_field.unwrap(); match header_field { Some(ref s) => assert_eq!(category_name, s), None => assert!(false, "Header field not present"), } } } #[inline] fn mk_category_storeid(s: &str) -> Result { crate::module_path::new_id(s).context(err_msg("Store id handling error")).map_err(Error::from) } #[inline] fn represents_category(store: &Store, sid: StoreId, name: &str) -> Result { store.exists(sid.clone()) .context(err_msg("Store id handling error")) .map_err(Error::from) .and_then(|bl| { if bl { store.get(sid) .context(err_msg("Store read error")) .map_err(Error::from) .and_then(|fle| { if let Some(fle) = fle { fle.get_header() .read_string(&String::from(CATEGORY_REGISTER_NAME_FIELD_PATH)) .context(EM::EntryHeaderReadError)? .ok_or_else(|| Error::from(EM::EntryHeaderTypeError)) .map(|s| s == name) } else { Ok(false) } }) } else { Ok(bl) // false } }) }