diff --git a/lib/entry/libimagentrycategory/Cargo.toml b/lib/entry/libimagentrycategory/Cargo.toml index 472d1b1e..70c6ee02 100644 --- a/lib/entry/libimagentrycategory/Cargo.toml +++ b/lib/entry/libimagentrycategory/Cargo.toml @@ -20,14 +20,16 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" } maintenance = { status = "actively-developed" } [dependencies] -log = "0.4.0" -toml = "0.4" -toml-query = "0.6" -is-match = "0.1" +log = "0.4.0" +toml = "0.4" +toml-query = "0.6" error-chain = "0.11" -libimagerror = { version = "0.8.0", path = "../../../lib/core/libimagerror" } -libimagstore = { version = "0.8.0", path = "../../../lib/core/libimagstore" } +libimagerror = { version = "0.8.0", path = "../../../lib/core/libimagerror" } +libimagstore = { version = "0.8.0", path = "../../../lib/core/libimagstore" } +libimagutil = { version = "0.8.0", path = "../../../lib/etc/libimagutil" } +libimagentryutil = { version = "0.8.0", path = "../../../lib/entry/libimagentryutil" } +libimagentrylink = { version = "0.8.0", path = "../../../lib/entry/libimagentrylink" } [dev-dependencies] env_logger = "0.5" diff --git a/lib/entry/libimagentrycategory/src/category.rs b/lib/entry/libimagentrycategory/src/category.rs index cd0e09ac..fab8715f 100644 --- a/lib/entry/libimagentrycategory/src/category.rs +++ b/lib/entry/libimagentrycategory/src/category.rs @@ -17,81 +17,48 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use toml_query::insert::TomlValueInsertExt; -use toml_query::read::TomlValueReadExt; -use toml_query::read::TomlValueReadTypeExt; -use toml::Value; - +use libimagentryutil::isa::Is; +use libimagentryutil::isa::IsKindHeaderPathProvider; use libimagstore::store::Entry; +use libimagstore::store::Store; +use libimagstore::storeid::StoreIdIterator; +use libimagentrylink::internal::InternalLinker; + +use toml_query::read::TomlValueReadTypeExt; -use error::CategoryErrorKind as CEK; -use error::CategoryError as CE; -use error::ResultExt; use error::Result; -use register::CategoryRegister; +use error::CategoryError as CE; +use error::CategoryErrorKind as CEK; +use store::CATEGORY_REGISTER_NAME_FIELD_PATH; +use iter::CategoryEntryIterator; -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub struct Category(String); - -impl From for Category { - - fn from(s: String) -> Category { - Category(s) - } +provide_kindflag_path!(pub IsCategory, "category.is_category"); +pub trait Category { + fn is_category(&self) -> Result; + fn get_name(&self) -> Result; + fn get_entries<'a>(&self, store: &'a Store) -> Result>; } -impl Into for Category { - fn into(self) -> String { - self.0 - } -} - -pub trait EntryCategory { - - fn set_category(&mut self, s: Category) -> Result<()>; - - fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()>; - - fn get_category(&self) -> Result>; - - fn has_category(&self) -> Result; - -} - -impl EntryCategory for Entry { - - fn set_category(&mut self, s: Category) -> Result<()> { - self.get_header_mut() - .insert(&String::from("category.value"), Value::String(s.into())) - .chain_err(|| CEK::HeaderWriteError) - .map(|_| ()) +impl Category for Entry { + fn is_category(&self) -> Result { + self.is::().map_err(CE::from) } - /// Check whether a category exists before setting it. - /// - /// This function should be used by default over EntryCategory::set_category()! - fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()> { - register.category_exists(&s.0) - .and_then(|bl| if bl { - self.set_category(s) - } else { - Err(CE::from_kind(CEK::CategoryDoesNotExist)) - }) - } - - fn get_category(&self) -> Result> { + fn get_name(&self) -> Result { + trace!("Getting category name of '{:?}'", self.get_location()); self.get_header() - .read_string("category.value") - .chain_err(|| CEK::HeaderReadError) - .and_then(|o| o.map(Category::from).ok_or(CE::from_kind(CEK::TypeError))) - .map(Some) + .read_string(CATEGORY_REGISTER_NAME_FIELD_PATH) + .map_err(CE::from)? + .ok_or_else(|| CE::from_kind(CEK::CategoryNameMissing)) } - fn has_category(&self) -> Result { - self.get_header().read("category.value") - .chain_err(|| CEK::HeaderReadError) - .map(|x| x.is_some()) + fn get_entries<'a>(&self, store: &'a Store) -> Result> { + trace!("Getting linked entries for category '{:?}'", self.get_location()); + let sit = self.get_internal_links()?.map(|l| l.get_store_id().clone()); + let sit = StoreIdIterator::new(Box::new(sit)); + let name = self.get_name()?; + Ok(CategoryEntryIterator::new(store, sit, name)) } - } + diff --git a/lib/entry/libimagentrycategory/src/entry.rs b/lib/entry/libimagentrycategory/src/entry.rs new file mode 100644 index 00000000..c6f82e04 --- /dev/null +++ b/lib/entry/libimagentrycategory/src/entry.rs @@ -0,0 +1,85 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2018 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::TomlValueReadExt; +use toml_query::read::TomlValueReadTypeExt; +use toml::Value; + +use libimagstore::store::Entry; +use libimagentrylink::internal::InternalLinker; + +use error::CategoryErrorKind as CEK; +use error::CategoryError as CE; +use error::ResultExt; +use error::Result; +use store::CategoryStore; + +pub trait EntryCategory { + + fn set_category(&mut self, s: &str) -> Result<()>; + + fn set_category_checked(&mut self, register: &CategoryStore, s: &str) -> Result<()>; + + fn get_category(&self) -> Result; + + fn has_category(&self) -> Result; + +} + +impl EntryCategory for Entry { + + fn set_category(&mut self, s: &str) -> Result<()> { + trace!("Setting category '{}' UNCHECKED", s); + self.get_header_mut() + .insert(&String::from("category.value"), Value::String(s.to_string())) + .chain_err(|| CEK::HeaderWriteError) + .map(|_| ()) + } + + /// Check whether a category exists before setting it. + /// + /// This function should be used by default over EntryCategory::set_category()! + fn set_category_checked(&mut self, register: &CategoryStore, s: &str) -> Result<()> { + trace!("Setting category '{}' checked", s); + let mut category = register + .get_category_by_name(s)? + .ok_or_else(|| CE::from_kind(CEK::CategoryDoesNotExist))?; + + let _ = self.set_category(s)?; + let _ = self.add_internal_link(&mut category)?; + + Ok(()) + } + + fn get_category(&self) -> Result { + trace!("Getting category from '{}'", self.get_location()); + self.get_header() + .read_string("category.value")? + .ok_or_else(|| CE::from_kind(CEK::CategoryNameMissing)) + } + + fn has_category(&self) -> Result { + trace!("Has category? '{}'", self.get_location()); + self.get_header().read("category.value") + .chain_err(|| CEK::HeaderReadError) + .map(|x| x.is_some()) + } + +} diff --git a/lib/entry/libimagentrycategory/src/error.rs b/lib/entry/libimagentrycategory/src/error.rs index 9cf446e6..f9b90ff3 100644 --- a/lib/entry/libimagentrycategory/src/error.rs +++ b/lib/entry/libimagentrycategory/src/error.rs @@ -24,6 +24,8 @@ error_chain! { links { StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind); + LinkError(::libimagentrylink::error::LinkError, ::libimagentrylink::error::LinkErrorKind); + EntryUtilError(::libimagentryutil::error::EntryUtilError, ::libimagentryutil::error::EntryUtilErrorKind); } foreign_links { @@ -65,6 +67,11 @@ error_chain! { description("Type Error") display("Type Error") } + + CategoryNameMissing { + description("Category name is missing") + display("Category name is missing") + } } } diff --git a/lib/entry/libimagentrycategory/src/iter.rs b/lib/entry/libimagentrycategory/src/iter.rs new file mode 100644 index 00000000..a835b82a --- /dev/null +++ b/lib/entry/libimagentrycategory/src/iter.rs @@ -0,0 +1,116 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2018 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 libimagstore::storeid::StoreIdIterator; +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; + +use toml_query::read::TomlValueReadTypeExt; + +use error::Result; +use error::CategoryError as CE; +use error::CategoryErrorKind as CEK; +use store::CATEGORY_REGISTER_NAME_FIELD_PATH; +use entry::EntryCategory; +use error::ResultExt; + +/// Iterator for Category names +/// +/// Iterates over Result +/// +/// # Return values +/// +/// In each iteration, a Option> is returned. Error kinds are as follows: +/// +/// * CategoryErrorKind::StoreReadError if a name could not be fetched from the store +/// * CategoryErrorKind::HeaderReadError if the header of the fetched item couldn't be read +/// * CategoryErrorKind::TypeError if the name could not be fetched because it is not a String +/// +pub struct CategoryNameIter<'a>(&'a Store, StoreIdIterator); + +impl<'a> CategoryNameIter<'a> { + + pub(crate) fn new(store: &'a Store, sidit: StoreIdIterator) -> CategoryNameIter<'a> { + CategoryNameIter(store, sidit) + } + +} + +impl<'a> Iterator for CategoryNameIter<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + // TODO: Optimize me with lazy_static + let query = CATEGORY_REGISTER_NAME_FIELD_PATH; + + while let Some(sid) = self.1.next() { + if sid.is_in_collection(&["category"]) { + let func = |store: &Store| { // hack for returning Some(Result<_, _>) + store + .get(sid)? + .ok_or_else(|| CE::from_kind(CEK::StoreReadError))? + .get_header() + .read_string(query) + .chain_err(|| CEK::HeaderReadError)? + .ok_or_else(|| CE::from_kind(CEK::StoreReadError)) + }; + + return Some(func(&self.0)) + } // else continue + } + + None + } +} + +pub struct CategoryEntryIterator<'a>(&'a Store, StoreIdIterator, String); + +impl<'a> CategoryEntryIterator<'a> { + pub(crate) fn new(store: &'a Store, sit: StoreIdIterator, name: String) -> Self { + CategoryEntryIterator(store, sit, name) + } +} + +impl<'a> Iterator for CategoryEntryIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option { + while let Some(next) = self.1.next() { + let getter = |next| -> Result<(String, FileLockEntry<'a>)> { + let entry = self.0 + .get(next)? + .ok_or_else(|| CE::from_kind(CEK::StoreReadError))?; + Ok((entry.get_category()?, entry)) + }; + + match getter(next) { + Err(e) => return Some(Err(e)), + Ok((c, e)) => { + if c == self.2 { + return Some(Ok(e)) + // } else { + // continue + } + } + } + } + + None + } +} diff --git a/lib/entry/libimagentrycategory/src/lib.rs b/lib/entry/libimagentrycategory/src/lib.rs index 18eb6901..0f0e1189 100644 --- a/lib/entry/libimagentrycategory/src/lib.rs +++ b/lib/entry/libimagentrycategory/src/lib.rs @@ -38,18 +38,20 @@ extern crate toml_query; extern crate toml; #[macro_use] -extern crate is_match; -#[macro_use] extern crate log; #[macro_use] extern crate error_chain; extern crate libimagerror; -#[macro_use] -extern crate libimagstore; +#[macro_use] extern crate libimagstore; +extern crate libimagutil; +#[macro_use] extern crate libimagentryutil; +extern crate libimagentrylink; pub mod category; +pub mod entry; pub mod error; -pub mod register; +pub mod store; +pub mod iter; module_entry_path_mod!("category"); diff --git a/lib/entry/libimagentrycategory/src/register.rs b/lib/entry/libimagentrycategory/src/store.rs similarity index 65% rename from lib/entry/libimagentrycategory/src/register.rs rename to lib/entry/libimagentrycategory/src/store.rs index aa75ab4f..e1fb929a 100644 --- a/lib/entry/libimagentrycategory/src/register.rs +++ b/lib/entry/libimagentrycategory/src/store.rs @@ -26,24 +26,25 @@ use toml::Value; use libimagstore::store::Store; use libimagstore::store::FileLockEntry; use libimagstore::storeid::StoreId; -use libimagstore::storeid::StoreIdIterator; +use libimagentryutil::isa::Is; -use category::Category; use error::CategoryErrorKind as CEK; use error::CategoryError as CE; use error::ResultExt; use error::Result; +use iter::CategoryNameIter; +use 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 CategoryRegister { +pub trait CategoryStore { fn category_exists(&self, name: &str) -> Result; - fn create_category(&self, name: &str) -> Result; + fn create_category<'a>(&'a self, name: &str) -> Result>; fn delete_category(&self, name: &str) -> Result<()>; @@ -53,10 +54,11 @@ pub trait CategoryRegister { } -impl CategoryRegister for Store { +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(self.path().clone(), name)?; represents_category(self, sid, name) } @@ -64,43 +66,31 @@ impl CategoryRegister for Store { /// Create a category /// /// Fails if the category already exists (returns false then) - fn create_category(&self, name: &str) -> Result { - use libimagstore::error::StoreErrorKind as SEK; + fn create_category<'a>(&'a self, name: &str) -> Result> { + trace!("Creating category: '{}'", name); + let sid = mk_category_storeid(self.path().clone(), name)?; + let mut entry = self.create(sid)?; - let sid = mk_category_storeid(self.path().clone(), name)?; + entry.set_isflag::()?; + let _ = entry + .get_header_mut() + .insert(CATEGORY_REGISTER_NAME_FIELD_PATH, Value::String(String::from(name)))?; - match self.create(sid) { - Ok(mut entry) => { - let val = Value::String(String::from(name)); - entry.get_header_mut() - .insert(CATEGORY_REGISTER_NAME_FIELD_PATH, val) - .map(|opt| if opt.is_none() { - debug!("Setting category header worked") - } else { - warn!("Setting category header replaced existing value: {:?}", opt); - }) - .map(|_| true) - .chain_err(|| CEK::HeaderWriteError) - .chain_err(|| CEK::StoreWriteError) - } - Err(store_error) => if is_match!(store_error.kind(), &SEK::EntryAlreadyExists(_)) { - Ok(false) - } else { - Err(store_error).chain_err(|| CEK::StoreWriteError) - } - } + trace!("Creating category worked: '{}'", name); + Ok(entry) } /// Delete a category fn delete_category(&self, name: &str) -> Result<()> { + trace!("Deleting category: '{}'", name); let sid = mk_category_storeid(self.path().clone(), name)?; - - self.delete(sid).chain_err(|| CEK::StoreWriteError) + self.delete(sid).map_err(CE::from) } /// Get all category names fn all_category_names(&self) -> Result { + trace!("Getting all category names"); Ok(CategoryNameIter::new(self, self.entries()?.without_store())) } @@ -109,6 +99,7 @@ impl CategoryRegister for Store { /// 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(self.path().clone(), name)?; self.get(sid) @@ -148,8 +139,6 @@ mod tests { let res = store.create_category(category_name); assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); - let res = res.unwrap(); - assert!(res); } #[test] @@ -157,11 +146,10 @@ mod tests { 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 res = res.unwrap(); - assert!(res); + { + 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))); @@ -176,11 +164,11 @@ mod tests { 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 res = res.unwrap(); - assert!(res); + { + let res = store.create_category(category_name); + assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); + } let id = PathBuf::from(format!("category/{}", category_name)); println!("Trying: {:?}", id); @@ -237,53 +225,3 @@ fn represents_category(store: &Store, sid: StoreId, name: &str) -> Result }) } -/// Iterator for Category names -/// -/// Iterates over Result -/// -/// # Return values -/// -/// In each iteration, a Option> is returned. Error kinds are as follows: -/// -/// * CategoryErrorKind::StoreReadError if a name could not be fetched from the store -/// * CategoryErrorKind::HeaderReadError if the header of the fetched item couldn't be read -/// * CategoryErrorKind::TypeError if the name could not be fetched because it is not a String -/// -pub struct CategoryNameIter<'a>(&'a Store, StoreIdIterator); - -impl<'a> CategoryNameIter<'a> { - - fn new(store: &'a Store, sidit: StoreIdIterator) -> CategoryNameIter<'a> { - CategoryNameIter(store, sidit) - } - -} - -impl<'a> Iterator for CategoryNameIter<'a> { - type Item = Result; - - fn next(&mut self) -> Option { - // TODO: Optimize me with lazy_static - let query = CATEGORY_REGISTER_NAME_FIELD_PATH; - - while let Some(sid) = self.1.next() { - if sid.is_in_collection(&["category"]) { - let func = |store: &Store| { // hack for returning Some(Result<_, _>) - store - .get(sid)? - .ok_or_else(|| CE::from_kind(CEK::StoreReadError))? - .get_header() - .read_string(query) - .chain_err(|| CEK::HeaderReadError)? - .map(Category::from) - .ok_or_else(|| CE::from_kind(CEK::StoreReadError)) - }; - - return Some(func(&self.0)) - } // else continue - } - - None - } -} -