libimagentrylink 0.10.1

Library for the imag core distribution
Documentation
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2020 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 failure::ResultExt;
use failure::Fallible as Result;
use failure::Error;
use failure::err_msg;

use crate::linkable::*;

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
                            .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 {
            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(())
            };

        let nw = aggregate_link_network(&self)?;

        for (k, v) in nw.iter() {
            debug!("{}\n in: {:?}\n out: {:?}", k, v.incoming, v.outgoing);
        }

        all_collected_storeids_exist(&nw).context("Link handling error")?;

        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(())
    }
}