mycelium_core 0.1.1

Library for Mycelium DDM
Documentation
use serde::Serialize;
use uuid::Uuid;

use std::collections::HashMap;
use std::path::PathBuf;

use crate::ephemeral::{create_page, Page};
use crate::persistence::Persistable;
use crate::{NodeId, PageId};
use mycelium_command::node::Node;

const FILENAME: &str = "hist.idx";
const HISTORY_TAG: &str = ".hist";
pub(crate) type Head = HashMap<NodeId, Vec<PageId>>;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct History {
    pub(crate) head: Head,
    last_inserted_page: Option<PageId>,
    head_path: std::path::PathBuf,
    max_page_size: usize,
    #[serde(skip)]
    pub(crate) pages: HashMap<PageId, Page>,
    working_dir: std::path::PathBuf,
}

// Load last_inserted_page into memory to be ready for updates
// Track changes and save on SaveScope
pub(crate) fn lazy_head(path: &PathBuf, max: usize) -> History {
    let mut working_dir = path.to_path_buf();
    working_dir.push(".hist");
    let mut head_path = working_dir.to_path_buf();
    head_path.push(FILENAME);

    let hist = crate::persistence::history::read_head(&head_path);
    if hist.is_some() {
        hist.unwrap()
    } else {
        new(head_path, working_dir, max)
    }
}

pub(crate) fn new(head_path: PathBuf, working_dir: PathBuf, max: usize) -> History {
    History {
        head: HashMap::new(),
        last_inserted_page: None,
        head_path,
        max_page_size: max,
        pages: HashMap::new(),
        working_dir,
    }
}

impl History {
    /// Append a node to the history. This doesn't remove it from primary and can contain
    /// duplicates.
    /// Typical flow -
    /// Update replaces node in primary structure
    /// Update returns original node
    /// Original node is appended to history after removed from primary.
    #[allow(clippy::map_entry)]
    pub(crate) fn append_history(&mut self, node: Node) -> Result<(), Box<dyn std::error::Error>> {
        let page = self.get_append_page(node.get_size());
        let hist_page_id = page.add(node.clone());
        if self.head.contains_key(&node.get_id()) {
            if !self.head[&node.get_id()].contains(&hist_page_id) {
                let h = self
                    .head
                    .get_mut(&hist_page_id)
                    .expect("Unable to edit history page index.");
                h.push(hist_page_id);
            }
        } else {
            self.head.insert(node.get_id(), vec![hist_page_id]);
        }

        Ok(())
    }

    pub(crate) fn clear(&mut self) -> std::io::Result<()> {
        self.pages.clear();
        Ok(())
    }

    pub(crate) fn get_append_page(&mut self, node_size: usize) -> &mut Page {
        // Check size. If page.size() + node.size() < page.max_size add
        if self.last_inserted_page.is_some() {
            let id = self.last_inserted_page.unwrap();
            match self.load_page(id) {
                Ok(_) => (),
                Err(e) => error!("Error: {:?}", e),
            }
            let page_ref = &self.pages[&id]; // self.pages.get(&id).unwrap();
            if page_ref.get_size() + node_size < page_ref.get_max_size() {
                let id = page_ref.get_id();
                return self.pages.get_mut(&id).unwrap();
            }
        }

        // get a new page either the other is full or we have not inserted yet
        let new_page = create_page(HISTORY_TAG, self.max_page_size);
        let new_page_id = new_page.get_id();
        self.pages.insert(new_page_id, new_page);
        self.last_inserted_page = Some(new_page_id);
        self.pages.get_mut(&new_page_id).unwrap()
    }

    pub(crate) fn get_working_directory(&self) -> PathBuf {
        self.working_dir.to_path_buf()
    }

    pub(crate) fn get_node_pages(&self, node_id: NodeId) -> Vec<PageId> {
        let mut list = vec![];
        if self.head.contains_key(&node_id) {
            for x in &self.head[&node_id] {
                list.push(*x); //copy
            }
        }
        list
    }

    pub(crate) fn get_nodes(&self, id: NodeId, limit: usize) -> Vec<Node> {
        let mut nodes: Vec<Node> = vec![];
        for p in self.pages.values() {
            for i in p.get_nodes() {
                if i.get_id() == id {
                    nodes.push(i.clone());
                }

                if limit > 0 && nodes.len() == limit {
                    break;
                }
            }
        }

        nodes
    }

    pub(crate) fn load_page(
        &mut self,
        page_id: PageId,
    ) -> std::result::Result<(), Box<dyn std::error::Error>> {
        let mut page_path = self.working_dir.to_path_buf();
        let id = Uuid::from_bytes(page_id);
        page_path.push(id.to_string());
        let p = [page_path];
        self.pages = crate::persistence::load_from_vec(p.to_vec())?;
        Ok(())
    }
}

impl Persistable for History {
    fn get_id_str(&self) -> &str {
        FILENAME
    }

    fn save(&self, path: &std::path::PathBuf) -> std::io::Result<()> {
        crate::persistence::persist(self, path)
    }
}