diff --git a/lib/entry/libimagentrylink/Cargo.toml b/lib/entry/libimagentrylink/Cargo.toml index 985039cc..2b48aa87 100644 --- a/lib/entry/libimagentrylink/Cargo.toml +++ b/lib/entry/libimagentrylink/Cargo.toml @@ -27,14 +27,20 @@ url = "1.5" sha-1 = "0.7" hex = "0.3" is-match = "0.1" -toml-query = "0.9" failure = "0.1" failure_derive = "0.1" +serde = "1" +serde_derive = "1" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" } +[dependencies.toml-query] +version = "0.9" +default-features = false +features = [ "typed" ] + [dev-dependencies] env_logger = "0.5" diff --git a/lib/entry/libimagentrylink/src/iter.rs b/lib/entry/libimagentrylink/src/iter.rs index 0b522826..818712ac 100644 --- a/lib/entry/libimagentrylink/src/iter.rs +++ b/lib/entry/libimagentrylink/src/iter.rs @@ -54,6 +54,8 @@ impl Iterator for LinkIter { } } + + pub trait IntoValues { fn into_values(self) -> Vec>; } diff --git a/lib/entry/libimagentrylink/src/lib.rs b/lib/entry/libimagentrylink/src/lib.rs index 5861c672..13ec9718 100644 --- a/lib/entry/libimagentrylink/src/lib.rs +++ b/lib/entry/libimagentrylink/src/lib.rs @@ -44,6 +44,8 @@ extern crate toml_query; extern crate url; extern crate sha1; extern crate hex; +extern crate serde; +#[macro_use] extern crate serde_derive; #[macro_use] extern crate failure; #[macro_use] extern crate is_match; diff --git a/lib/entry/libimagentrylink/src/link.rs b/lib/entry/libimagentrylink/src/link.rs index 36ff0379..82370111 100644 --- a/lib/entry/libimagentrylink/src/link.rs +++ b/lib/entry/libimagentrylink/src/link.rs @@ -23,7 +23,6 @@ use libimagstore::store::Store; use libimagerror::errors::ErrorMsg as EM; use toml::Value; -use toml::map::Map; use failure::ResultExt; use failure::Fallible as Result; use failure::Error; @@ -31,7 +30,6 @@ use failure::Error; #[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)] pub enum Link { Id { link: StoreId }, - Annotated { link: StoreId, annotation: String }, } impl Link { @@ -39,7 +37,6 @@ impl Link { pub fn exists(&self, store: &Store) -> Result { match *self { Link::Id { ref link } => store.exists(link.clone()), - Link::Annotated { ref link, .. } => store.exists(link.clone()), } .map_err(From::from) } @@ -47,16 +44,14 @@ impl Link { pub fn to_str(&self) -> Result { match *self { Link::Id { ref link } => link.to_str(), - Link::Annotated { ref link, .. } => link.to_str(), } .map_err(From::from) } - + #[cfg(test)] pub(crate) fn eq_store_id(&self, id: &StoreId) -> bool { match self { &Link::Id { link: ref s } => s.eq(id), - &Link::Annotated { link: ref s, .. } => s.eq(id), } } @@ -64,30 +59,16 @@ impl Link { pub fn get_store_id(&self) -> &StoreId { match self { &Link::Id { link: ref s } => s, - &Link::Annotated { link: ref s, .. } => s, } } pub(crate) fn to_value(&self) -> Result { match self { - &Link::Id { link: ref s } => - s.to_str() + Link::Id { ref link } => link + .to_str() .map(Value::String) .context(EM::ConversionError) .map_err(Error::from), - &Link::Annotated { ref link, annotation: ref anno } => { - link.to_str() - .map(Value::String) - .context(EM::ConversionError) - .map_err(Error::from) - .map(|link| { - let mut tab = Map::new(); - - tab.insert("link".to_owned(), link); - tab.insert("annotation".to_owned(), Value::String(anno.clone())); - Value::Table(tab) - }) - } } } @@ -97,10 +78,6 @@ impl ::std::cmp::PartialEq for Link { fn eq(&self, other: &Self) -> bool { match (self, other) { (&Link::Id { link: ref a }, &Link::Id { link: ref b }) => a.eq(&b), - (&Link::Annotated { link: ref a, annotation: ref ann1 }, - &Link::Annotated { link: ref b, annotation: ref ann2 }) => - (a, ann1).eq(&(b, ann2)), - _ => false, } } } @@ -116,7 +93,6 @@ impl Into for Link { fn into(self) -> StoreId { match self { Link::Id { link } => link, - Link::Annotated { link, .. } => link, } } } @@ -125,7 +101,6 @@ impl IntoStoreId for Link { fn into_storeid(self) -> Result { match self { Link::Id { link } => Ok(link), - Link::Annotated { link, .. } => Ok(link), } } } @@ -134,7 +109,6 @@ impl AsRef for Link { fn as_ref(&self) -> &StoreId { match self { &Link::Id { ref link } => &link, - &Link::Annotated { ref link, .. } => &link, } } } diff --git a/lib/entry/libimagentrylink/src/linkable.rs b/lib/entry/libimagentrylink/src/linkable.rs index e8eaf630..104e6421 100644 --- a/lib/entry/libimagentrylink/src/linkable.rs +++ b/lib/entry/libimagentrylink/src/linkable.rs @@ -17,27 +17,25 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // +use std::path::PathBuf; + use libimagstore::storeid::StoreId; use libimagstore::store::Entry; use libimagstore::store::Store; -use libimagerror::errors::ErrorMsg as EM; +use toml_query::read::Partial; use toml_query::read::TomlValueReadExt; use toml_query::insert::TomlValueInsertExt; use failure::ResultExt; use failure::Fallible as Result; -use failure::Error; use failure::err_msg; use crate::iter::LinkIter; -use crate::iter::IntoValues; use crate::link::Link; -use toml::Value; - pub trait Linkable { - /// Get the internal links from the implementor object + /// Get all links fn links(&self) -> Result; /// Add an internal link to the implementor object @@ -49,8 +47,24 @@ pub trait Linkable { /// Remove _all_ internal links fn unlink(&mut self, store: &Store) -> Result<()>; - /// Add internal annotated link - fn add_annotated_link(&mut self, link: &mut Entry, annotation: String) -> Result<()>; +} + +#[derive(Serialize, Deserialize, Debug)] +struct LinkPartial { + internal: Option>, +} + +impl Default for LinkPartial { + fn default() -> Self { + LinkPartial { + internal: None, + } + } +} + +impl<'a> Partial<'a> for LinkPartial { + const LOCATION: &'static str = "links"; + type Output = Self; } impl Linkable for Entry { @@ -58,44 +72,71 @@ impl Linkable for Entry { fn links(&self) -> Result { debug!("Getting internal links"); trace!("Getting internal links from header of '{}' = {:?}", self.get_location(), self.get_header()); - let res = self + + let partial : LinkPartial = self .get_header() - .read("links.internal") - .context(format_err!("Failed to read header 'links.internal' of '{}'", self.get_location())) - .context(EM::EntryHeaderReadError) - .context(EM::EntryHeaderError) - .map_err(Error::from) - .map(|r| r.cloned()); - process_rw_result(res) + .read_partial::()? + .unwrap_or_else(|| LinkPartial::default()); + + partial + .internal + .unwrap_or_else(|| vec![]) + .into_iter() + .map(PathBuf::from) + .map(StoreId::new) + .map(|r| r.map(Link::from)) + .collect::>>() + .map(LinkIter::new) } - fn add_link(&mut self, link: &mut Entry) -> Result<()> { - debug!("Adding internal link: {:?}", link); - let location = link.get_location().clone().into(); - add_link_with_instance(self, link, location) + fn add_link(&mut self, other: &mut Entry) -> Result<()> { + debug!("Adding internal link: {:?}", other); + let left_location = self.get_location().to_str()?; + let right_location = other.get_location().to_str()?; + + alter_linking(self, other, |mut left, mut right| { + let mut left_internal = left.internal.unwrap_or_else(|| vec![]); + left_internal.push(right_location); + + left_internal.sort_unstable(); + left_internal.dedup(); + + let mut right_internal = right.internal.unwrap_or_else(|| vec![]); + right_internal.push(left_location); + + right_internal.sort_unstable(); + right_internal.dedup(); + + left.internal = Some(left_internal); + right.internal = Some(right_internal); + + Ok((left, right)) + }) } - fn remove_link(&mut self, link: &mut Entry) -> Result<()> { - debug!("Removing internal link: {:?}", link); + fn remove_link(&mut self, other: &mut Entry) -> Result<()> { + debug!("Remove internal link: {:?}", other); + let left_location = self.get_location().to_str()?; + let right_location = other.get_location().to_str()?; - // Cloning because of borrowing - let own_loc = self.get_location().clone(); - let other_loc = link.get_location().clone(); + alter_linking(self, other, |mut left, mut right| { + let mut left_internal = left.internal.unwrap_or_else(|| vec![]); + left_internal.retain(|l| *l != right_location); - debug!("Removing internal link from {:?} to {:?}", own_loc, other_loc); + left_internal.sort_unstable(); + left_internal.dedup(); - let links = link.links()?; - debug!("Rewriting own links for {:?}, without {:?}", other_loc, own_loc); + let mut right_internal = right.internal.unwrap_or_else(|| vec![]); + right_internal.retain(|l| *l != left_location); - let links = links.filter(|l| !l.eq_store_id(&own_loc)); - let _ = rewrite_links(link.get_header_mut(), links)?; + right_internal.sort_unstable(); + right_internal.dedup(); - self.links() - .and_then(|links| { - debug!("Rewriting own links for {:?}, without {:?}", own_loc, other_loc); - let links = links.filter(|l| !l.eq_store_id(&other_loc)); - rewrite_links(self.get_header_mut(), links) - }) + left.internal = Some(left_internal); + right.internal = Some(right_internal); + + Ok((left, right)) + }) } fn unlink(&mut self, store: &Store) -> Result<()> { @@ -109,147 +150,33 @@ impl Linkable for Entry { Ok(()) } - fn add_annotated_link(&mut self, link: &mut Entry, annotation: String) -> Result<()> { - let new_link = Link::Annotated { - link: link.get_location().clone(), - annotation: annotation, - }; - - add_link_with_instance(self, link, new_link) - } - } -fn add_link_with_instance(this: &mut Entry, link: &mut Entry, instance: Link) -> Result<()> { - debug!("Adding internal link from {:?} to {:?}", this.get_location(), instance); +fn alter_linking(left: &mut Entry, right: &mut Entry, f: F) -> Result<()> + where F: FnOnce(LinkPartial, LinkPartial) -> Result<(LinkPartial, LinkPartial)> +{ + debug!("Altering linkage of {:?} and {:?}", left, right); - add_foreign_link(link, this.get_location().clone()) - .and_then(|_| { - this.links() - .and_then(|links| { - let links = links.chain(LinkIter::new(vec![instance])); - rewrite_links(this.get_header_mut(), links) - }) - }) -} - -fn rewrite_links>(header: &mut Value, links: I) -> Result<()> { - let links = links.into_values() - .into_iter() - .fold(Ok(vec![]), |acc: Result>, elem| { - acc.and_then(move |mut v| { - v.push(elem.context(EM::ConversionError)?); - Ok(v) - }) - })?; - - debug!("Setting new link array: {:?}", links); - let process = header - .insert("links.internal", Value::Array(links)) - .context(format_err!("Failed to insert header 'links.internal'")) - .context(EM::EntryHeaderReadError) - .map_err(Error::from); - process_rw_result(process).map(|_| ()) -} - -/// When Linking A -> B, the specification wants us to link back B -> A. -/// This is a helper function which does this. -fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> { - debug!("Linking back from {:?} to {:?}", target.get_location(), from); - target.links() - .and_then(|links| { - let links = links - .chain(LinkIter::new(vec![from.into()])) - .into_values() - .into_iter() - .fold(Ok(vec![]), |acc: Result>, elem| { - acc.and_then(move |mut v| { - v.push(elem.context(EM::ConversionError)?); - Ok(v) - }) - })?; - debug!("Setting links in {:?}: {:?}", target.get_location(), links); - - let res = target - .get_header_mut() - .insert("links.internal", Value::Array(links)) - .context(format_err!("Failed to insert header 'links.internal'")) - .context(EM::EntryHeaderReadError) - .map_err(Error::from); - - process_rw_result(res).map(|_| ()) - }) -} - -fn process_rw_result(links: Result>) -> Result { - use std::path::PathBuf; - - let links = match links { - Err(e) => { - debug!("RW action on store failed. Generating LinkError"); - return Err(e).context(EM::EntryHeaderReadError).map_err(Error::from) - }, - Ok(None) => { - debug!("We got no value from the header!"); - return Ok(LinkIter::new(vec![])) - }, - Ok(Some(Value::Array(l))) => l, - Ok(Some(_)) => { - debug!("We expected an Array for the links, but there was a non-Array!"); - return Err(err_msg("Link type error")); - } + let get_partial = |entry: &mut Entry| -> Result { + Ok(entry.get_header().read_partial::()?.unwrap_or_else(|| LinkPartial::default())) }; - if !links.iter().all(|l| is_match!(*l, Value::String(_)) || is_match!(*l, Value::Table(_))) { - debug!("At least one of the Values which were expected in the Array of links is not a String or a Table!"); - debug!("Generating LinkError"); - return Err(err_msg("Existing Link type error")); - } + let left_partial : LinkPartial = get_partial(left)?; + let right_partial : LinkPartial = get_partial(right)?; - let links : Vec = links.into_iter() - .map(|link| { - debug!("Matching the link: {:?}", link); - match link { - Value::String(s) => StoreId::new(PathBuf::from(s)) - .map(|s| Link::Id { link: s }) - .map_err(From::from) - , - Value::Table(mut tab) => { - debug!("Destructuring table"); - if !tab.contains_key("link") - || !tab.contains_key("annotation") { - debug!("Things missing... returning Error instance"); - Err(err_msg("Link parser error")) - } else { - let link = tab.remove("link") - .ok_or(err_msg("Link parser: field missing"))?; + trace!("Partial left before: {:?}", left_partial); + trace!("Partial right before: {:?}", right_partial); - let anno = tab.remove("annotation") - .ok_or(err_msg("Link parser: Field missing"))?; + let (left_partial, right_partial) = f(left_partial, right_partial)?; - debug!("Ok, here we go with building a Link::Annotated"); - match (link, anno) { - (Value::String(link), Value::String(anno)) => { - StoreId::new(PathBuf::from(link)) - .map_err(From::from) - .map(|link| { - Link::Annotated { - link: link, - annotation: anno, - } - }) - }, - _ => Err(err_msg("Link parser: Field type error")), - } - } - } - _ => unreachable!(), - } - }) - .collect::>>()?; + trace!("Partial left after: {:?}", left_partial); + trace!("Partial right after: {:?}", right_partial); - debug!("Ok, the RW action was successful, returning link vector now!"); - Ok(LinkIter::new(links)) + left.get_header_mut().insert_serialized("links", left_partial)?; + right.get_header_mut().insert_serialized("links", right_partial)?; + + debug!("Finished altering linkage!"); + Ok(()) } #[cfg(test)] @@ -259,7 +186,6 @@ mod test { use libimagstore::store::Store; use super::Linkable; - use super::Link; fn setup_logging() { let _ = ::env_logger::try_init(); @@ -291,7 +217,9 @@ mod test { assert!(e2.links().is_ok()); { - assert!(e1.add_link(&mut e2).is_ok()); + let res = e1.add_link(&mut e2); + debug!("Result = {:?}", res); + assert!(res.is_ok()); let e1_links = e1.links().unwrap().collect::>(); let e2_links = e2.links().unwrap().collect::>(); @@ -464,33 +392,4 @@ mod test { assert_eq!(e3.links().unwrap().collect::>().len(), 0); } - #[test] - fn test_link_annotating() { - setup_logging(); - let store = get_store(); - let mut entry1 = store.create(PathBuf::from("test_link_annotating-1")).unwrap(); - let mut entry2 = store.create(PathBuf::from("test_link_annotating-2")).unwrap(); - - let res = entry1.add_annotated_link(&mut entry2, String::from("annotation")); - assert!(res.is_ok()); - - { - for link in entry1.links().unwrap() { - match link { - Link::Annotated {annotation, ..} => assert_eq!(annotation, "annotation"), - _ => assert!(false, "Non-annotated link found"), - } - } - } - - { - for link in entry2.links().unwrap() { - match link { - Link::Id {..} => {}, - Link::Annotated {..} => assert!(false, "Annotated link found"), - } - } - } - } - } diff --git a/lib/entry/libimagentrymarkdown/src/processor.rs b/lib/entry/libimagentrymarkdown/src/processor.rs index f6138e6a..98b10db2 100644 --- a/lib/entry/libimagentrymarkdown/src/processor.rs +++ b/lib/entry/libimagentrymarkdown/src/processor.rs @@ -438,7 +438,7 @@ mod tests { assert!(result.is_ok(), "Should be Ok(()): {:?}", result); // The hash of "http://example.com" processed in the `libimagentrylink` way. - let expected_link = "url/external/9c17e047f58f9220a7008d4f18152fee4d111d14"; + let expected_link = "url/9c17e047f58f9220a7008d4f18152fee4d111d14"; { let base_links = base.links(); assert!(base_links.is_ok()); diff --git a/lib/entry/libimagentryurl/Cargo.toml b/lib/entry/libimagentryurl/Cargo.toml index 2c8ede95..5b17404b 100644 --- a/lib/entry/libimagentryurl/Cargo.toml +++ b/lib/entry/libimagentryurl/Cargo.toml @@ -27,15 +27,21 @@ url = "1.5" sha-1 = "0.7" hex = "0.3" is-match = "0.1" -toml-query = "0.9" failure = "0.1" failure_derive = "0.1" +serde = "1" +serde_derive = "1" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" } libimagentrylink = { version = "0.10.0", path = "../../../lib/entry/libimagentrylink" } +[dependencies.toml-query] +version = "0.9" +default-features = false +features = [ "typed" ] + [dev-dependencies] env_logger = "0.5" diff --git a/lib/entry/libimagentryurl/src/iter.rs b/lib/entry/libimagentryurl/src/iter.rs index 73c5f135..ddec1d6c 100644 --- a/lib/entry/libimagentryurl/src/iter.rs +++ b/lib/entry/libimagentryurl/src/iter.rs @@ -37,7 +37,7 @@ use libimagutil::debug_result::DebugResult; use failure::Fallible as Result; use url::Url; -/// Helper for building `OnlyExternalIter` and `NoExternalIter` +/// Helper for building `OnlyUrlIter` and `NoUrlIter` /// /// The boolean value defines, how to interpret the `is_external_link_storeid()` return value /// (here as "pred"): @@ -55,9 +55,9 @@ use url::Url; /// false... and so on. /// /// As we can see, the operator between these two operants is `!(a ^ b)`. -pub struct ExternalFilterIter(LinkIter, bool); +pub struct UrlFilterIter(LinkIter, bool); -impl Iterator for ExternalFilterIter { +impl Iterator for UrlFilterIter { type Item = Link; fn next(&mut self) -> Option { @@ -78,24 +78,24 @@ impl Iterator for ExternalFilterIter { /// /// # See also /// -/// Also see `OnlyExternalIter` and `NoExternalIter` and the helper traits/functions -/// `OnlyInteralLinks`/`only_links()` and `OnlyExternalLinks`/`only_urls()`. -pub trait SelectExternal { - fn select_urls(self, b: bool) -> ExternalFilterIter; +/// Also see `OnlyUrlIter` and `NoUrlIter` and the helper traits/functions +/// `OnlyInteralLinks`/`only_links()` and `OnlyUrlLinks`/`only_urls()`. +pub trait SelectUrl { + fn select_urls(self, b: bool) -> UrlFilterIter; } -impl SelectExternal for LinkIter { - fn select_urls(self, b: bool) -> ExternalFilterIter { - ExternalFilterIter(self, b) +impl SelectUrl for LinkIter { + fn select_urls(self, b: bool) -> UrlFilterIter { + UrlFilterIter(self, b) } } -pub struct OnlyExternalIter(ExternalFilterIter); +pub struct OnlyUrlIter(UrlFilterIter); -impl OnlyExternalIter { - pub fn new(li: LinkIter) -> OnlyExternalIter { - OnlyExternalIter(ExternalFilterIter(li, true)) +impl OnlyUrlIter { + pub fn new(li: LinkIter) -> OnlyUrlIter { + OnlyUrlIter(UrlFilterIter(li, true)) } pub fn urls<'a>(self, store: &'a Store) -> UrlIter<'a> { @@ -103,7 +103,7 @@ impl OnlyExternalIter { } } -impl Iterator for OnlyExternalIter { +impl Iterator for OnlyUrlIter { type Item = Link; fn next(&mut self) -> Option { @@ -111,15 +111,15 @@ impl Iterator for OnlyExternalIter { } } -pub struct NoExternalIter(ExternalFilterIter); +pub struct NoUrlIter(UrlFilterIter); -impl NoExternalIter { - pub fn new(li: LinkIter) -> NoExternalIter { - NoExternalIter(ExternalFilterIter(li, false)) +impl NoUrlIter { + pub fn new(li: LinkIter) -> NoUrlIter { + NoUrlIter(UrlFilterIter(li, false)) } } -impl Iterator for NoExternalIter { +impl Iterator for NoUrlIter { type Item = Link; fn next(&mut self) -> Option { @@ -127,35 +127,35 @@ impl Iterator for NoExternalIter { } } -pub trait OnlyExternalLinks : Sized { - fn only_urls(self) -> OnlyExternalIter ; +pub trait OnlyUrlLinks : Sized { + fn only_urls(self) -> OnlyUrlIter ; - fn no_links(self) -> OnlyExternalIter { + fn no_links(self) -> OnlyUrlIter { self.only_urls() } } -impl OnlyExternalLinks for LinkIter { - fn only_urls(self) -> OnlyExternalIter { - OnlyExternalIter::new(self) +impl OnlyUrlLinks for LinkIter { + fn only_urls(self) -> OnlyUrlIter { + OnlyUrlIter::new(self) } } pub trait OnlyInternalLinks : Sized { - fn only_links(self) -> NoExternalIter; + fn only_links(self) -> NoUrlIter; - fn no_urls(self) -> NoExternalIter { + fn no_urls(self) -> NoUrlIter { self.only_links() } } impl OnlyInternalLinks for LinkIter { - fn only_links(self) -> NoExternalIter { - NoExternalIter::new(self) + fn only_links(self) -> NoUrlIter { + NoUrlIter::new(self) } } -pub struct UrlIter<'a>(OnlyExternalIter, &'a Store); +pub struct UrlIter<'a>(OnlyUrlIter, &'a Store); impl<'a> Iterator for UrlIter<'a> { type Item = Result; @@ -174,8 +174,8 @@ impl<'a> Iterator for UrlIter<'a> { .map_err(From::from) .and_then(|f| { debug!("Store::retrieve({:?}) succeeded", id); - debug!("getting external link from file now"); - f.get_link_uri_from_filelockentry() + debug!("getting uri link from file now"); + f.get_url() .map_dbg_str("Error happened while getting link URI from FLE") .map_dbg_err(|e| format!("URL -> Err = {:?}", e)) }) diff --git a/lib/entry/libimagentryurl/src/lib.rs b/lib/entry/libimagentryurl/src/lib.rs index 7d08ba14..4333a734 100644 --- a/lib/entry/libimagentryurl/src/lib.rs +++ b/lib/entry/libimagentryurl/src/lib.rs @@ -55,6 +55,8 @@ extern crate toml_query; extern crate url; extern crate sha1; extern crate hex; +extern crate serde; +#[macro_use] extern crate serde_derive; #[macro_use] extern crate failure; #[cfg(test)] diff --git a/lib/entry/libimagentryurl/src/link.rs b/lib/entry/libimagentryurl/src/link.rs index 60f0bf88..8c5e8e98 100644 --- a/lib/entry/libimagentryurl/src/link.rs +++ b/lib/entry/libimagentryurl/src/link.rs @@ -22,55 +22,122 @@ use failure::ResultExt; use failure::Fallible as Result; use failure::err_msg; use url::Url; +use toml_query::read::Partial; +use toml_query::insert::TomlValueInsertExt; +use toml::Value; use libimagstore::store::Entry; use libimagerror::errors::ErrorMsg as EM; -use toml_query::read::TomlValueReadTypeExt; +use toml_query::read::TomlValueReadExt; pub trait Link { - - fn get_link_uri_from_filelockentry(&self) -> Result>; - fn get_url(&self) -> Result>; - + fn set_url(&mut self, url: Url) -> Result<()>; } +#[derive(Deserialize, Serialize, Debug)] +pub(crate) struct UrlHeader { + pub uri: Option, +} + +impl Default for UrlHeader { + fn default() -> Self { + UrlHeader { + uri: None + } + } +} + +impl<'a> Partial<'a> for UrlHeader { + const LOCATION: &'static str = "url"; + type Output = Self; +} + + impl Link for Entry { - fn get_link_uri_from_filelockentry(&self) -> Result> { - self.get_header() - .read_string("links.external.content.url") - .context(format_err!("Error reading header 'links.external.content.url' from '{}'", self.get_location())) + /// Get the URL from entry Entry + /// + /// # Notice + /// + /// This actually returns the header field of the entry, parsed as URL + /// + /// + fn get_url(&self) -> Result> { + let partial = self.get_header() + .read_partial::() + .context(format_err!("Error reading header 'url.uri' from '{}'", self.get_location())) .context(EM::EntryHeaderReadError) + .map_err(Error::from)? + .unwrap_or_else(|| Default::default()); + + debug!("Partial deserialized: {:?}", partial); + + let url = match partial.uri { + Some(uri) => uri, + None => return Ok(None), + }; + + debug!("Found url, parsing: {:?}", url); + Url::parse(&url) .map_err(Error::from) - .and_then(|opt| match opt { - None => Ok(None), - Some(ref s) => { - debug!("Found url, parsing: {:?}", s); - Url::parse(&s[..]) - .map_err(Error::from) - .context(format_err!("Failed to parse URL: '{}'", s)) - .context(err_msg("Invalid URI")) - .map_err(Error::from) - .map(Some) - }, - }) + .context(format_err!("Failed to parse URL: '{}'", url)) + .context(err_msg("Invalid URI")) + .map_err(Error::from) + .map(Some) .context("Failed to get link URI from entry") .map_err(Error::from) } - fn get_url(&self) -> Result> { - match self.get_header().read_string("links.external.url")? { - None => Ok(None), - Some(ref s) => Url::parse(&s[..]) - .context(format_err!("Failed to parse URL: '{}'", s)) - .map(Some) - .map_err(Error::from) - .context(EM::EntryHeaderReadError) - .map_err(Error::from), + fn set_url(&mut self, url: Url) -> Result<()> { + let val = Value::String(url.to_string()); + self.get_header_mut().insert_serialized("url.uri", val)?; + + debug!("Setting URL worked"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + use libimagstore::store::Store; + + fn setup_logging() { + let _ = env_logger::try_init(); + } + + pub fn get_store() -> Store { + Store::new_inmemory(PathBuf::from("/"), &None).unwrap() + } + + #[test] + fn test_header_set_correctly() { + setup_logging(); + let store = get_store(); + let mut e = store.retrieve(PathBuf::from("urlentry")).unwrap(); + let url = Url::parse("http://google.de").unwrap(); + + assert!(e.set_url(url).is_ok()); + debug!("Fetch header: {:?}", e.get_header()); + + let url = e.get_header().read("url.uri"); + + debug!("Fetched header: {:?}", url); + + assert!(url.is_ok()); + let url = url.unwrap(); + + assert!(url.is_some()); + let url = url.unwrap(); + + match url { + Value::String(ref s) => assert_eq!("http://google.de/", s), + _ => assert!(false), } } } - diff --git a/lib/entry/libimagentryurl/src/linker.rs b/lib/entry/libimagentryurl/src/linker.rs index eccbcc1f..a6d9a3ad 100644 --- a/lib/entry/libimagentryurl/src/linker.rs +++ b/lib/entry/libimagentryurl/src/linker.rs @@ -17,8 +17,6 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use std::ops::DerefMut; - use libimagstore::storeid::StoreId; use libimagstore::store::Store; use libimagstore::store::Entry; @@ -26,77 +24,82 @@ use libimagutil::debug_result::DebugResult; use libimagentrylink::linkable::Linkable; use failure::Fallible as Result; -use toml::Value; -use toml::map::Map; -use toml_query::read::TomlValueReadExt; -use toml_query::insert::TomlValueInsertExt; use url::Url; use sha1::{Sha1, Digest}; use hex; +use crate::link::Link; use crate::iter::UrlIter; pub trait UrlLinker : Linkable { - /// Get the external links from the implementor object fn get_urls<'a>(&self, store: &'a Store) -> Result>; - /// Set the external links for the implementor object fn set_urls(&mut self, store: &Store, links: Vec) -> Result>; - /// Add an external link to the implementor object fn add_url(&mut self, store: &Store, link: Url) -> Result>; - /// Remove an external link from the implementor object fn remove_url(&mut self, store: &Store, link: Url) -> Result>; } -/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external -/// link in an entry, but internal links to other entries which serve as external links, as one -/// entry in the store can only have one external link. impl UrlLinker for Entry { - /// Get the external links from the implementor object + /// Get URLs from the Entry + /// + /// + /// # Notice + /// + /// (Also see documentation of `UrlLinker::set_urls()`) + /// + /// This fetches all Links (as in `libimagentrylink` for the Entry and filters them by entries + /// which contain an URL. + /// + /// + /// # Return Value + /// + /// Iterator over URLs + /// fn get_urls<'a>(&self, store: &'a Store) -> Result> { - use crate::iter::OnlyExternalLinks; + use crate::iter::OnlyUrlLinks; // Iterate through all internal links and filter for FileLockEntries which live in - // /link/external/ -> load these files and get the external link from their headers, + // /url/ -> load these files and get the url from their headers, // put them into the return vector. self.links() .map(|iter| { - debug!("Getting external links"); + debug!("Getting urls"); iter.only_urls().urls(store) }) } - /// Set the external links for the implementor object + /// Set URLs for the Entry + /// + /// # Notice + /// + /// This does not actually add each URL in this entry, but retrieves (as in + /// `Store::retrieve()`) one entry for each URL and links (as in `libimagentrylink`) this entry + /// to the retrieved ones. + /// /// /// # Return Value /// - /// Returns the StoreIds which were newly created for the new external links, if there are more - /// external links than before. - /// If there are less external links than before, an empty vec![] is returned. + /// Returns the StoreIds which were newly created for the new urls, if there are more + /// urls than before. + /// If there are less urls than before, an empty vec![] is returned. /// fn set_urls(&mut self, store: &Store, links: Vec) -> Result> { - // Take all the links, generate a SHA sum out of each one, filter out the already existing - // store entries and store the other URIs in the header of one FileLockEntry each, in - // the path /link/external/ - debug!("Iterating {} links = {:?}", links.len(), links); links.into_iter().map(|link| { let hash = hex::encode(Sha1::digest(&link.as_str().as_bytes())); - let file_id = crate::module_path::new_id(format!("external/{}", hash)) - .map_dbg_err(|_| { - format!("Failed to build StoreId for this hash '{:?}'", hash) - })?; + let file_id = crate::module_path::new_id(hash.clone()) + .map_dbg_err(|_| format!("Failed to build StoreId for this hash '{:?}'", hash))?; debug!("Link = '{:?}'", link); debug!("Hash = '{:?}'", hash); debug!("StoreId = '{:?}'", file_id); - let link_already_exists = store.get(file_id.clone())?.is_some(); + let link_already_exists = store.exists(file_id.clone())?; // retrieve the file from the store, which implicitely creates the entry if it does not // exist @@ -107,31 +110,11 @@ impl UrlLinker for Entry { })?; debug!("Generating header content!"); - { - let hdr = file.deref_mut().get_header_mut(); - - let mut table = match hdr.read("links.external.content")? { - Some(&Value::Table(ref table)) => table.clone(), - Some(_) => { - warn!("There is a value at 'links.external.content' which is not a table."); - warn!("Going to override this value"); - Map::new() - }, - None => Map::new(), - }; - - let v = Value::String(link.into_string()); - - debug!("setting URL = '{:?}", v); - table.insert(String::from("url"), v); - - let _ = hdr.insert("links.external.content", Value::Table(table))?; - debug!("Setting URL worked"); - } + file.set_url(link)?; // then add an internal link to the new file or return an error if this fails - let _ = self.add_link(file.deref_mut())?; - debug!("Added internal link"); + let _ = self.add_link(&mut file)?; + debug!("Added linking: {:?} <-> {:?}", self.get_location(), file.get_location()); Ok((link_already_exists, file_id)) }) @@ -142,52 +125,40 @@ impl UrlLinker for Entry { .collect() } - /// Add an external link to the implementor object + /// Add an URL to the entry + /// + /// + /// # Notice + /// + /// (Also see documentation of `UrlLinker::set_urls()`) + /// /// /// # Return Value /// - /// (See ExternalLinker::set_urls()) - /// - /// Returns the StoreIds which were newly created for the new external links, if there are more - /// external links than before. - /// If there are less external links than before, an empty vec![] is returned. + /// (See UrlLinker::set_urls()) /// fn add_url(&mut self, store: &Store, link: Url) -> Result> { - // get external links, add this one, save them - debug!("Getting links"); - self.get_urls(store) - .and_then(|links| { - let mut links = links.collect::>>()?; - - debug!("Adding link = '{:?}' to links = {:?}", link, links); - links.push(link); - - debug!("Setting {} links = {:?}", links.len(), links); - self.set_urls(store, links) - }) + let mut links = self.get_urls(store)?.collect::>>()?; + links.push(link); + self.set_urls(store, links) } - /// Remove an external link from the implementor object + /// Remove an URL from the entry + /// + /// + /// # Notice + /// + /// (Also see documentation of `UrlLinker::set_urls()`) + /// /// /// # Return Value /// - /// (See ExternalLinker::set_urls()) - /// - /// Returns the StoreIds which were newly created for the new external links, if there are more - /// external links than before. - /// If there are less external links than before, an empty vec![] is returned. + /// (See UrlLinker::set_urls()) /// fn remove_url(&mut self, store: &Store, link: Url) -> Result> { - // get external links, remove this one, save them - self.get_urls(store) - .and_then(|links| { - debug!("Removing link = '{:?}'", link); - let links = links - .filter_map(Result::ok) - .filter(|l| l.as_str() != link.as_str()) - .collect::>(); - self.set_urls(store, links) - }) + let mut links = self.get_urls(store)?.collect::>>()?; + links.retain(|l| *l != link); + self.set_urls(store, links) } } @@ -207,6 +178,47 @@ mod tests { Store::new_inmemory(PathBuf::from("/"), &None).unwrap() } + #[test] + fn test_adding_url() { + use toml_query::read::TomlValueReadTypeExt; + + setup_logging(); + let store = get_store(); + let mut e = store.retrieve(PathBuf::from("base-test_simple")).unwrap(); + let url = Url::parse("http://google.de").unwrap(); + + assert!(e.add_url(&store, url.clone()).is_ok()); + + debug!("{:?}", e); + debug!("Header: {:?}", e.get_header()); + + let link = e.links().unwrap().next(); + assert!(link.is_some()); + let link = link.unwrap(); + + debug!("link[0] = {:?}", link); + let id = link.get_store_id(); + + let link_entry = store.get(id.clone()).unwrap().unwrap(); + + debug!("Entry = {:?}", link_entry); + debug!("Header = {:?}", link_entry.get_header()); + + let link = match link_entry.get_header().read_string("url.uri") { + Ok(Some(s)) => s, + Ok(None) => { + assert!(false); + unreachable!() + }, + Err(e) => { + error!("{:?}", e); + assert!(false); + unreachable!() + }, + }; + + assert_eq!(link, "http://google.de/"); + } #[test] fn test_simple() { @@ -217,7 +229,22 @@ mod tests { assert!(e.add_url(&store, url.clone()).is_ok()); - assert_eq!(1, e.get_urls(&store).unwrap().count()); + debug!("{:?}", e); + debug!("Header: {:?}", e.get_header()); + + let urls = e.get_urls(&store); + let urls = match urls { + Err(e) => { + debug!("Error: {:?}", e); + assert!(false); + unreachable!() + }, + Ok(urls) => urls.collect::>(), + }; + + debug!("urls = {:?}", urls); + + assert_eq!(1, urls.len()); assert_eq!(url, e.get_urls(&store).unwrap().next().unwrap().unwrap()); } diff --git a/lib/entry/libimagentryurl/src/util.rs b/lib/entry/libimagentryurl/src/util.rs index 48d02fff..8ea64e3c 100644 --- a/lib/entry/libimagentryurl/src/util.rs +++ b/lib/entry/libimagentryurl/src/util.rs @@ -21,8 +21,8 @@ use std::fmt::Debug; use libimagstore::storeid::StoreId; -/// Check whether the StoreId starts with `/link/external/` +/// Check whether the StoreId starts with `/url/` pub fn is_external_link_storeid + Debug>(id: A) -> bool { - debug!("Checking whether this is a 'url/external/': '{:?}'", id); - id.as_ref().is_in_collection(&["url", "external"]) + debug!("Checking whether this is a 'url/': '{:?}'", id); + id.as_ref().is_in_collection(&["url"]) }