use super::*; use std::collections::HashMap; use std::path::{Path,PathBuf}; use std::borrow::Borrow; /// Contains a graph of all paths and inodes that were successfully stat'd #[derive(Debug, Clone, PartialEq, Eq)] pub struct INodeInfoGraph { inodes: HashMap, // FsInfo contains parent INode that can be used to look up again in this table paths: HashMap, // map absolute paths to INodes to be looked up in `inodes` table. paths_reverse: HashMap, // reverse lookup of INode to PathBuf, children: HashMap>, //reverse lookup for directory INodes and their parent INodes } #[derive(Debug, Clone)] pub struct INodeInfoGraphEntry<'a> { master: &'a INodeInfoGraph, inode: INode, } impl<'a> INodeInfoGraphEntry<'a> { /// Create an iterator over children of this graph item. /// /// # Note /// Returns `None` if this is not a directory item. pub fn level(&self) -> Option> { match self.master.inodes.get(&self.inode) { Some(FsInfo::Directory(parent)) => { Some( Level{ master: self.master, inode: self.inode.clone(), children: match self.master.children.get(&self.inode) { Some(iter) => iter.iter(), _ => [].iter(), }, } ) }, Some(FsInfo::File(_, _)) => None, None => { panic!("No lookup information for inode {:?}", self.inode) }//Some(Level{master: self.master, inode: None, children: [].iter()}), } } /// The `FsInfo` for this item. pub fn info(&self) -> &FsInfo { self.master.inodes.get(&self.inode).unwrap() } /// The path for this inode pub fn path(&self) -> &PathBuf { self.master.paths_reverse.get(&self.inode).unwrap() } } #[derive(Debug, Clone)] pub struct Level<'a> { master: &'a INodeInfoGraph, inode: INode,// Should only ever be `Directory` for `Level`. children: std::slice::Iter<'a, INode>, } #[derive(Debug, Clone)] pub struct TopLevel<'a> { master: &'a INodeInfoGraph, children: std::vec::IntoIter<&'a INode>, } impl<'a> Iterator for TopLevel<'a> { type Item = INodeInfoGraphEntry<'a>; fn next(&mut self) -> Option { let master = self.master; self.children.next().map(|inode| { INodeInfoGraphEntry{ master, inode: inode.clone(), } }) } } impl<'a> Iterator for Level<'a> { type Item = INodeInfoGraphEntry<'a>; fn next(&mut self) -> Option { let master = self.master; self.children.next().map(|inode| { INodeInfoGraphEntry{ master, inode: inode.clone(), } }) } } impl INodeInfoGraph { /// Total size of all files pub fn total_size(&self) -> u64 { self.inodes.iter().map(|(_, v)| { match v { FsInfo::File(len, _) => *len, _ => 0, } }).sum() } /// An iterator over the top level of items pub fn top_level(&self) -> TopLevel<'_> { let top_level = self.inodes.iter().filter(|(node, _)| { !self.inodes.contains_key(node) }); TopLevel { master: self, children: top_level.map(|(k, _)| k).collect::>().into_iter(), } } /// Create a new graph from these linked `HashMap`s #[inline] pub fn new(inodes: HashMap, paths: HashMap) -> Self { Self { children: HashMap::with_capacity(inodes.len()), paths_reverse: HashMap::with_capacity(paths.len()), inodes, paths, } .compute_child_table() .compute_reverse_path_table() } #[inline] fn compute_child_table(mut self) -> Self { for (node, info) in self.inodes.iter() { match info { FsInfo::Directory(parent_node) | FsInfo::File(_, parent_node) => { self.children.entry(*parent_node).or_insert_with(|| Vec::new()).push(*node); }, } } self } #[inline] fn compute_reverse_path_table(mut self) -> Self { self.paths_reverse.extend(self.paths.iter().map(|(x,y)| (*y,x.clone()))); self } /// Total number of items in the graph #[inline] pub fn len(&self) -> usize { self.children.len() } /// Get the FsInfo of this `INode` #[inline] pub fn get_info(&self, node: impl Borrow) -> Option<&FsInfo> { self.inodes.get(node.borrow()) } /* /// An iterator over top-level children of this node pub fn children_of(&self, node: impl Borrow) -> !//Children<'_> { todo!(); /*Children(self, match self.children.get(node.borrow()) { Some(slc) => slc.iter(), _ => [].iter(), })*/ } /// An iterator over all the directories in this pub fn directories(&self) -> !//Directories<'_> { todo!()//Directories(self, self.children.keys()) }*/ /// Convert into a hierarchical representation pub fn into_hierarchical(self) -> HierarchicalINodeGraph { HierarchicalINodeGraph::create(self) } /// Convert into a hierarchical representation pub fn create_hierarchical(&self) -> HierarchicalINodeGraph { HierarchicalINodeGraph::create_from(self) } } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature="inspect", derive(serde::Serialize, serde::Deserialize))] enum NodeKind { Directory(Vec, Option), File(u64), } impl NodeKind { pub fn len(&self) -> Option { match self { Self::File(f) => Some(*f), Self::Directory(_, o) => *o, } } } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature="inspect", derive(serde::Serialize, serde::Deserialize))] struct HierarchicalNode { kind: NodeKind, inode: INode, } impl HierarchicalNode { #[inline(always)] pub fn len(&self) -> Option { self.kind.len() } pub fn is_dir(&self) -> bool { match self.kind { NodeKind::Directory(_, _) => true, _ => false } } #[inline] pub fn is_file(&self) -> bool { !self.is_dir() } } /// A hierarchical graph of node sizes #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature="inspect", derive(serde::Serialize, serde::Deserialize))] pub struct HierarchicalINodeGraph { table: HashMap } impl HierarchicalINodeGraph { /// Compute the sizes of directories in the graph pub fn compute_recursive_sizes(&mut self) { fn compute_for_dir(this: &HashMap, children: &Vec) -> u64 { let mut total = 0; for child in children.iter() { let chl = if let Some(ch) = this.get(child) { match ch { HierarchicalNode { kind: NodeKind::Directory(_, Some(sz)), .. } => { total += sz; continue; }, HierarchicalNode { kind: NodeKind::Directory(children, _), .. } => { children }, HierarchicalNode { kind: NodeKind::File(sz), .. } => { total += sz; continue; }, } } else { continue; }; total += compute_for_dir(this, chl); } total } let mut length_proxy = HashMap::with_capacity(self.table.len()); for (path, hnode) in self.table.iter() { let (children, len) = match hnode { HierarchicalNode { kind: NodeKind::Directory(children, len), .. } => { (children, len) }, _ => continue, }; match len { Some(sz) => length_proxy.insert(path.clone(), *sz), None => length_proxy.insert(path.clone(), compute_for_dir(&self.table, children)), }; } for (pathref, sz) in length_proxy.into_iter() { self.table.get_mut(&pathref).map(|hnode| { match hnode { HierarchicalNode { kind: NodeKind::Directory(_, ss), .. } => { *ss = Some(sz); }, _ => (), } }); } } #[inline(always)] fn create(graph: INodeInfoGraph) -> Self { //eh... Self::create_from(&graph) } fn create_from(graph: &INodeInfoGraph) -> Self { let mut new = Self { table: HashMap::with_capacity(graph.inodes.len()), }; macro_rules! unwrap { ($expr:expr) => { match $expr { Some(ex) => ex, _ => continue, } }; } for (path, &inode) in graph.paths.iter() { let path = path.clone(); //Lookup the INode in graph inode table if let Some(fsinfo) = graph.inodes.get(&inode) { match fsinfo { FsInfo::File(sz, _) => { new.table.insert(path, HierarchicalNode { kind: NodeKind::File(*sz), inode, }); }, FsInfo::Directory(_) => { new.table.insert(path, HierarchicalNode { kind: NodeKind::Directory(unwrap!(graph.children.get(&inode)).iter().map(|node| { graph.paths_reverse.get(node).unwrap().clone() }).collect(), None), inode, }); }, } } } new } /// Get the max size and the path with this size pub fn path_max_size_for(&self, kind: FsObjKind) -> Option<(&Path, u64)> { self.table.iter().filter_map(|(path, hi)| { match kind { FsObjKind::File if hi.is_file() => hi.len().map(|x| (path.as_path(), x)), FsObjKind::Directory if hi.is_dir() => hi.len().map(|x| (path.as_path(), x)), _ => None } }).max_by_key(|x| x.1) } /// Get the max size and the path with this size, whether it's a `Directory` or a `File`. pub fn path_max_size(&self) -> Option<(&Path, u64)> { self.table.iter().filter_map(|(path, hi)| { hi.len().map(|x| (path.as_path(), x)) }).max_by_key(|x| x.1) } /// Complete number of items in the tree #[inline] pub fn len(&self) -> usize { self.table.len() } } impl From for HierarchicalINodeGraph { fn from(from: INodeInfoGraph) -> Self { Self::create(from) } } /// A file or directory #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] pub enum FsObjKind { Directory, File, }