diff options
Diffstat (limited to 'lib/entry/libimagentrylink/src/storecheck.rs')
-rw-r--r-- | lib/entry/libimagentrylink/src/storecheck.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/lib/entry/libimagentrylink/src/storecheck.rs b/lib/entry/libimagentrylink/src/storecheck.rs new file mode 100644 index 00000000..decf4cdd --- /dev/null +++ b/lib/entry/libimagentrylink/src/storecheck.rs @@ -0,0 +1,173 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> 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 std::collections::HashMap; + +use libimagstore::store::Store; +use libimagstore::storeid::StoreId; +use libimagutil::debug_result::DebugResult; + +use failure::ResultExt; +use failure::Fallible as Result; +use failure::Error; +use failure::err_msg; + +use crate::linker::*; + +pub trait StoreLinkConsistentExt { + fn check_link_consistency(&self) -> Result<()>; +} + +impl StoreLinkConsistentExt for Store { + fn check_link_consistency(&self) -> Result<()> { + // Helper data structure to collect incoming and outgoing links for each StoreId + #[derive(Debug, Default)] + struct Linking { + outgoing: Vec<StoreId>, + incoming: Vec<StoreId>, + } + + // 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<HashMap<StoreId, Linking>> { + store + .entries()? + .into_get_iter() + .fold(Ok(HashMap::new()), |map, element| { + map.and_then(|mut map| { + debug!("Checking element = {:?}", element); + let entry = element?.ok_or_else(|| err_msg("TODO: Not yet handled"))?; + + debug!("Checking entry = {:?}", entry.get_location()); + + let internal_links = entry + .get_internal_links()? + .into_getter(store); // get the FLEs from the Store + + let mut linking = Linking::default(); + for internal_link in internal_links { + debug!("internal link = {:?}", internal_link); + + linking.outgoing.push(internal_link?.get_location().clone()); + linking.incoming.push(entry.get_location().clone()); + } + + map.insert(entry.get_location().clone(), linking); + Ok(map) + }) + }) + }; + + // Helper to check whethre all StoreIds in the network actually exists + // + // Because why not? + let all_collected_storeids_exist = |network: &HashMap<StoreId, Linking>| -> Result<()> { + for (id, _) in network.iter() { + if is_match!(self.get(id.clone()), Ok(Some(_))) { + debug!("Exists in store: {:?}", id); + + if !self.exists(id.clone())? { + warn!("Does exist in store but not on FS: {:?}", id); + return Err(err_msg("Link target does not exist")) + } + } else { + warn!("Does not exist in store: {:?}", id); + return Err(err_msg("Link target does not exist")) + } + } + + Ok(()) + }; + + // Helper function to create a SLCECD::OneDirectionalLink error object + let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> Error { + Error::from(format_err!("Dead link: {} -> {}", + src.local_display_string(), + target.local_display_string())) + }; + + // 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<StoreId, Linking>| -> Result<()> { + for link in linking.incoming.iter() { + // 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 { + return Err(mk_one_directional_link_err(src.clone(), link.clone())) + } + } + + 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<StoreId, Linking>| -> Result<()> { + for link in linking.outgoing.iter() { + // 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 { + return Err(mk_one_directional_link_err(link.clone(), src.clone())) + } + } + + Ok(()) + }; + + aggregate_link_network(&self) + .map_dbg_str("Aggregated") + .map_dbg(|nw| { + let mut s = String::new(); + for (k, v) in nw { + s.push_str(&format!("{}\n in: {:?}\n out: {:?}", k, v.incoming, v.outgoing)); + } + s + }) + .and_then(|nw| { + all_collected_storeids_exist(&nw) + .map(|_| nw) + .context(err_msg("Link handling error")) + .map_err(Error::from) + }) + .and_then(|nw| { + for (id, linking) in nw.iter() { + incoming_links_exists_as_outgoing_links(id, linking, &nw)?; + outgoing_links_exist_as_incoming_links(id, linking, &nw)?; + } + Ok(()) + }) + .map(|_| ()) + } +} + |