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"]); + } + + } + }