You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dirstat/src/data/graph.rs

410 lines
9.4 KiB

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<INode, FsInfo>, // FsInfo contains parent INode that can be used to look up again in this table
paths: HashMap<PathBuf, INode>, // map absolute paths to INodes to be looked up in `inodes` table.
paths_reverse: HashMap<INode, PathBuf>, // reverse lookup of INode to PathBuf,
children: HashMap<INode, Vec<INode>>, //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<Level<'a>>
{
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<Self::Item>
{
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<Self::Item>
{
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::<Vec<_>>().into_iter(),
}
}
/// Create a new graph from these linked `HashMap`s
#[inline] pub fn new(inodes: HashMap<INode, FsInfo>, paths: HashMap<PathBuf, INode>) -> 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<INode>) -> Option<&FsInfo>
{
self.inodes.get(node.borrow())
}
/*
/// An iterator over top-level children of this node
pub fn children_of(&self, node: impl Borrow<INode>) -> !//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<PathBuf>, Option<u64>),
File(u64),
}
impl NodeKind
{
pub fn len(&self) -> Option<u64>
{
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<u64>
{
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<PathBuf, HierarchicalNode>
}
impl HierarchicalINodeGraph
{
/// Compute the sizes of directories in the graph
pub fn compute_recursive_sizes(&mut self)
{
fn compute_for_dir(this: &HashMap<PathBuf, HierarchicalNode>, children: &Vec<PathBuf>) -> 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<INodeInfoGraph> 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,
}