From 0eac6d9931577da49e3e546a8484ec039d41506e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 21 Jun 2019 22:30:03 +0200 Subject: [PATCH] Add support for directional links This patch adds support for directional links in libimagentrylink. A directional link is added to the same header as the "internal" links, which are unidirectional. The "outgoing" link is added in "links.to", the "incoming" links are added to "links.from". This way we can always traverse all linkage. The `Linkable::links()` method is changed to always return _all_ link. The `Link` type was extended for notions of "to" and "from" links. Also adds testing. Signed-off-by: Matthias Beyer --- lib/entry/libimagentrylink/src/link.rs | 31 +++- lib/entry/libimagentrylink/src/linkable.rs | 167 +++++++++++++++++++++ 2 files changed, 193 insertions(+), 5 deletions(-) diff --git a/lib/entry/libimagentrylink/src/link.rs b/lib/entry/libimagentrylink/src/link.rs index 82370111..b60e22ca 100644 --- a/lib/entry/libimagentrylink/src/link.rs +++ b/lib/entry/libimagentrylink/src/link.rs @@ -30,6 +30,8 @@ use failure::Error; #[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)] pub enum Link { Id { link: StoreId }, + LinkTo { link: StoreId }, + LinkFrom { link: StoreId }, } impl Link { @@ -37,6 +39,8 @@ impl Link { pub fn exists(&self, store: &Store) -> Result { match *self { Link::Id { ref link } => store.exists(link.clone()), + Link::LinkTo { ref link } => store.exists(link.clone()), + Link::LinkFrom { ref link } => store.exists(link.clone()), } .map_err(From::from) } @@ -44,6 +48,8 @@ impl Link { pub fn to_str(&self) -> Result { match *self { Link::Id { ref link } => link.to_str(), + Link::LinkTo { ref link } => link.to_str(), + Link::LinkFrom { ref link } => link.to_str(), } .map_err(From::from) } @@ -52,6 +58,8 @@ impl Link { pub(crate) fn eq_store_id(&self, id: &StoreId) -> bool { match self { &Link::Id { link: ref s } => s.eq(id), + &Link::LinkTo { ref link } => link.eq(id), + &Link::LinkFrom { ref link } => link.eq(id), } } @@ -59,17 +67,21 @@ impl Link { pub fn get_store_id(&self) -> &StoreId { match self { &Link::Id { link: ref s } => s, + &Link::LinkTo { ref link } => link, + &Link::LinkFrom { ref link } => link, } } pub(crate) fn to_value(&self) -> Result { match self { - Link::Id { ref link } => link - .to_str() - .map(Value::String) - .context(EM::ConversionError) - .map_err(Error::from), + Link::Id { ref link } => link, + Link::LinkTo { ref link } => link, + Link::LinkFrom { ref link } => link, } + .to_str() + .map(Value::String) + .context(EM::ConversionError) + .map_err(Error::from) } } @@ -78,6 +90,9 @@ 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::LinkTo { link: ref a }, &Link::LinkTo { link: ref b })=> a.eq(&b), + (&Link::LinkFrom { link: ref a }, &Link::LinkFrom { link: ref b })=> a.eq(&b), + _ => false, } } } @@ -93,6 +108,8 @@ impl Into for Link { fn into(self) -> StoreId { match self { Link::Id { link } => link, + Link::LinkTo { link } => link, + Link::LinkFrom { link } => link, } } } @@ -101,6 +118,8 @@ impl IntoStoreId for Link { fn into_storeid(self) -> Result { match self { Link::Id { link } => Ok(link), + Link::LinkTo { link } => Ok(link), + Link::LinkFrom { link } => Ok(link), } } } @@ -109,6 +128,8 @@ impl AsRef for Link { fn as_ref(&self) -> &StoreId { match self { &Link::Id { ref link } => &link, + &Link::LinkTo { ref link } => &link, + &Link::LinkFrom { ref link } => &link, } } } diff --git a/lib/entry/libimagentrylink/src/linkable.rs b/lib/entry/libimagentrylink/src/linkable.rs index 104e6421..2c07af4b 100644 --- a/lib/entry/libimagentrylink/src/linkable.rs +++ b/lib/entry/libimagentrylink/src/linkable.rs @@ -38,6 +38,15 @@ pub trait Linkable { /// Get all links fn links(&self) -> Result; + /// Get all links which are unidirectional links + fn unidirectional_links(&self) -> Result; + + /// Get all links which are directional links, outgoing + fn directional_links_to(&self) -> Result; + + /// Get all links which are directional links, incoming + fn directional_links_from(&self) -> Result; + /// Add an internal link to the implementor object fn add_link(&mut self, link: &mut Entry) -> Result<()>; @@ -47,17 +56,27 @@ pub trait Linkable { /// Remove _all_ internal links fn unlink(&mut self, store: &Store) -> Result<()>; + /// Add a directional link: self -> otehr + fn add_link_to(&mut self, other: &mut Entry) -> Result<()>; + + /// Remove a directional link: self -> otehr + fn remove_link_to(&mut self, other: &mut Entry) -> Result<()>; + } #[derive(Serialize, Deserialize, Debug)] struct LinkPartial { internal: Option>, + from: Option>, + to: Option>, } impl Default for LinkPartial { fn default() -> Self { LinkPartial { internal: None, + from: None, + to: None, } } } @@ -82,6 +101,8 @@ impl Linkable for Entry { .internal .unwrap_or_else(|| vec![]) .into_iter() + .chain(partial.from.unwrap_or_else(|| vec![]).into_iter()) + .chain(partial.to.unwrap_or_else(|| vec![]).into_iter()) .map(PathBuf::from) .map(StoreId::new) .map(|r| r.map(Link::from)) @@ -89,6 +110,48 @@ impl Linkable for Entry { .map(LinkIter::new) } + /// Get all links which are unidirectional links + fn unidirectional_links(&self) -> Result { + trace!("Getting unidirectional links from header of '{}' = {:?}", self.get_location(), self.get_header()); + + let iter = self.get_header() + .read_partial::()? + .unwrap_or_else(Default::default) + .internal + .unwrap_or_else(|| vec![]) + .into_iter(); + + link_string_iter_to_link_iter(iter) + } + + /// Get all links which are directional links, outgoing + fn directional_links_to(&self) -> Result { + trace!("Getting unidirectional (to) links from header of '{}' = {:?}", self.get_location(), self.get_header()); + + let iter = self.get_header() + .read_partial::()? + .unwrap_or_else(Default::default) + .to + .unwrap_or_else(|| vec![]) + .into_iter(); + + link_string_iter_to_link_iter(iter) + } + + /// Get all links which are directional links, incoming + fn directional_links_from(&self) -> Result { + trace!("Getting unidirectional (from) links from header of '{}' = {:?}", self.get_location(), self.get_header()); + + let iter = self.get_header() + .read_partial::()? + .unwrap_or_else(Default::default) + .from + .unwrap_or_else(|| vec![]) + .into_iter(); + + link_string_iter_to_link_iter(iter) + } + fn add_link(&mut self, other: &mut Entry) -> Result<()> { debug!("Adding internal link: {:?}", other); let left_location = self.get_location().to_str()?; @@ -150,6 +213,53 @@ impl Linkable for Entry { Ok(()) } + fn add_link_to(&mut self, other: &mut Entry) -> Result<()> { + 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_to = left.to.unwrap_or_else(|| vec![]); + left_to.push(right_location); + + let mut right_from = right.from.unwrap_or_else(|| vec![]); + right_from.push(left_location); + + left.to = Some(left_to); + right.from = Some(right_from); + + Ok((left, right)) + }) + } + + /// Remove a directional link: self -> otehr + fn remove_link_to(&mut self, other: &mut Entry) -> Result<()> { + 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_to = left.to.unwrap_or_else(|| vec![]); + left_to.retain(|l| *l != right_location); + + let mut right_from = right.from.unwrap_or_else(|| vec![]); + right_from.retain(|l| *l != left_location); + + left.to = Some(left_to); + right.from = Some(right_from); + + Ok((left, right)) + }) + } + +} + +fn link_string_iter_to_link_iter(iter: I) -> Result + where I: Iterator +{ + iter.map(PathBuf::from) + .map(StoreId::new) + .map(|r| r.map(Link::from)) + .collect::>>() + .map(LinkIter::new) } fn alter_linking(left: &mut Entry, right: &mut Entry, f: F) -> Result<()> @@ -392,4 +502,61 @@ mod test { assert_eq!(e3.links().unwrap().collect::>().len(), 0); } + #[test] + fn test_directional_link() { + use libimagstore::store::Entry; + + setup_logging(); + let store = get_store(); + let mut entry1 = store.create(PathBuf::from("test_directional_link-1")).unwrap(); + let mut entry2 = store.create(PathBuf::from("test_directional_link-2")).unwrap(); + + assert!(entry1.unidirectional_links().unwrap().collect::>().is_empty()); + assert!(entry2.unidirectional_links().unwrap().collect::>().is_empty()); + + assert!(entry1.directional_links_to().unwrap().collect::>().is_empty()); + assert!(entry2.directional_links_to().unwrap().collect::>().is_empty()); + + assert!(entry1.directional_links_from().unwrap().collect::>().is_empty()); + assert!(entry2.directional_links_from().unwrap().collect::>().is_empty()); + + assert!(entry1.add_link_to(&mut entry2).is_ok()); + + assert_eq!(entry1.unidirectional_links().unwrap().collect::>(), vec![]); + assert_eq!(entry2.unidirectional_links().unwrap().collect::>(), vec![]); + + let get_directional_links_to = |e: &Entry| -> Result, _> { + e.directional_links_to() + .unwrap() + .map(|l| l.to_str()) + .collect::, _>>() + }; + + let get_directional_links_from = |e: &Entry| { + e.directional_links_from() + .unwrap() + .map(|l| l.to_str()) + .collect::, _>>() + }; + + { + let entry1_dir_links = get_directional_links_to(&entry1).unwrap(); + assert_eq!(entry1_dir_links, vec!["test_directional_link-2"]); + } + { + let entry2_dir_links = get_directional_links_to(&entry2).unwrap(); + assert!(entry2_dir_links.is_empty()); + } + + { + let entry1_dir_links = get_directional_links_from(&entry1).unwrap(); + assert!(entry1_dir_links.is_empty()); + } + { + let entry2_dir_links = get_directional_links_from(&entry2).unwrap(); + assert_eq!(entry2_dir_links, vec!["test_directional_link-1"]); + } + + } + }