diff --git a/libimagentrylink/src/internal.rs b/libimagentrylink/src/internal.rs index 9c2cc63e..3ce9e5f7 100644 --- a/libimagentrylink/src/internal.rs +++ b/libimagentrylink/src/internal.rs @@ -586,6 +586,7 @@ fn process_rw_result(links: StoreResult>) -> Result { } pub mod store_check { + use libimagstore::store::Store; pub mod error { generate_error_imports!(); @@ -610,7 +611,9 @@ pub mod store_check { StoreLinkConsistencyError, StoreLinkConsistencyErrorKind, StoreLinkConsistencyErrorCustomData, - StoreLinkConsistencyError => "Links in the store are not consistent" + StoreLinkConsistencyError => "Links in the store are not consistent", + LinkHandlingError => "Error in link handling", + StoreError => "Error while talking to the store" ); generate_result_helper!(StoreLinkConsistencyError, StoreLinkConsistencyErrorKind); @@ -629,8 +632,194 @@ pub mod store_check { } pub trait StoreLinkConsistentExt { + fn check_link_consistency(&self) -> Result<()>; + } + + impl StoreLinkConsistentExt for Store { fn check_link_consistency(&self) -> Result<()> { - unimplemented!() + use std::collections::HashMap; + + use self::error::StoreLinkConsistencyErrorKind as SLCEK; + use self::error::StoreLinkConsistencyError as SLCE; + use self::error::StoreLinkConsistencyErrorCustomData as SLCECD; + use error::LinkErrorKind as LEK; + use result::Result as LResult; + use internal::InternalLinker; + + use libimagstore::store::StoreObject; + use libimagstore::storeid::StoreId; + use libimagerror::iter::TraceIterator; + use libimagerror::into::IntoError; + use libimagutil::iter::FoldResult; + + // Helper data structure to collect incoming and outgoing links for each StoreId + #[derive(Debug, Default)] + struct Linking { + outgoing: Vec, + incoming: Vec, + } + + /// Helper function to aggregate the Link network + /// + /// This function aggregates a HashMap which maps each StoreId object in the store onto + /// a Linking object, which contains a list of StoreIds which this entry links to and a + /// list of StoreIds which link to the current one. + /// + /// The lambda returns an error if something fails + let aggregate_link_network = |store: &Store| -> Result> { + store + .walk("") // this is a hack... I know... + .filter_map(|obj: StoreObject| match obj { + StoreObject::Id(id) => Some(id), + _ => None + }) // Only ids are interesting + .fold(Ok(HashMap::new()), |acc, sid| { + acc.and_then(|mut state| { + debug!("Checking entry: '{}'", sid); + + match try!(self.get(sid).map_err_into(SLCEK::StoreError)) { + Some(fle) => { + debug!("Found FileLockEntry"); + + let fle_loc = fle.get_location(); + + let internal_links = fle + .get_internal_links() + .map_err_into(SLCEK::StoreError)? + .into_getter(self) // get the FLEs from the Store + .trace_unwrap(); // trace all Err(e)s and get the Ok(fle)s + + for internal_link in internal_links { + let il_loc = internal_link.get_location(); + + state + .entry(il_loc.clone()) + .or_insert(Linking::default()) + .incoming + .push(fle_loc.clone()); + + // Make sure an empty linking object is present for the + // current StoreId object + state + .entry(fle_loc.clone()) + .or_insert(Linking::default()) + .outgoing + .push(il_loc.clone()); + } + + Ok(state) + }, + None => { + debug!("No entry"); + Ok(state) + } + } + }) + }) + }; + + /// Helper to check whethre all StoreIds in the network actually exists + /// + /// Because why not? + let all_collected_storeids_exist = |network: &HashMap| -> LResult<()> { + network + .iter() + .fold_result(|(id, _)| { + if is_match!(self.get(id.clone()), Ok(Some(_))) { + debug!("Exists in store: {:?}", id); + + let exists = { + use error::MapErrInto as MEI; + try!(MEI::map_err_into(id.exists(), LEK::StoreReadError)) + }; + + if !exists { + warn!("Does exist in store but not on FS: {:?}", id); + Err(LEK::LinkTargetDoesNotExist.into_error()) + } else { + Ok(()) + } + } else { + warn!("Does not exist in store: {:?}", id); + Err(LEK::LinkTargetDoesNotExist.into_error()) + } + }) + }; + + /// Helper function to create a SLCECD::OneDirectionalLink error object + #[inline] + let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> SLCE { + // construct the error + let custom = SLCECD::OneDirectionalLink { + source: src, + target: target, + }; + + SLCEK::StoreLinkConsistencyError + .into_error() + .with_custom_data(custom) + }; + + /// Helper lambda to check whether the _incoming_ links of each entry actually also + /// appear in the _outgoing_ list of the linked entry + let incoming_links_exists_as_outgoing_links = + |src: &StoreId, linking: &Linking, network: &HashMap| -> Result<()> { + linking + .incoming + .iter() + .fold_result(|link| { + + // Check whether the links which are _incoming_ on _src_ are outgoing + // in each of the links in the incoming list. + let incoming_consistent = network.get(link) + .map(|l| l.outgoing.contains(src)) + .unwrap_or(false); + + if !incoming_consistent { + Err(mk_one_directional_link_err(src.clone(), link.clone())) + } else { + Ok(()) + } + }) + }; + + /// Helper lambda to check whether the _outgoing links of each entry actually also + /// appear in the _incoming_ list of the linked entry + let outgoing_links_exist_as_incoming_links = + |src: &StoreId, linking: &Linking, network: &HashMap| -> Result<()> { + linking + .outgoing + .iter() + .fold_result(|link| { + + // Check whether the links which are _outgoing_ on _src_ are incoming + // in each of the links in the outgoing list. + let outgoing_consistent = network.get(link) + .map(|l| l.incoming.contains(src)) + .unwrap_or(false); + + if !outgoing_consistent { + Err(mk_one_directional_link_err(link.clone(), src.clone())) + } else { + Ok(()) + } + }) + }; + + aggregate_link_network(&self) + .and_then(|nw| { + all_collected_storeids_exist(&nw) + .map(|_| nw) + .map_err_into(SLCEK::LinkHandlingError) + }) + .and_then(|nw| { + nw.iter().fold_result(|(id, linking)| { + try!(incoming_links_exists_as_outgoing_links(id, linking, &nw)); + try!(outgoing_links_exist_as_incoming_links(id, linking, &nw)); + Ok(()) + }) + }) + .map(|_| ()) } }