parent
6a105ea154
commit
5592bc5d1b
@ -0,0 +1,41 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Recursion
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Limited(usize),
|
||||||
|
Unlimited,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Recursion
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::Unlimited
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recursion
|
||||||
|
{
|
||||||
|
/// Can we run at this depth?
|
||||||
|
pub fn can_run(&self, depth: usize) -> bool
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::None => depth == 1,
|
||||||
|
Self::Limited(limit) => depth <= *limit,
|
||||||
|
Self::Unlimited => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for this run
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct Config
|
||||||
|
{
|
||||||
|
pub paths: Vec<PathBuf>,
|
||||||
|
pub recursive: Recursion,
|
||||||
|
pub max_tasks: Option<NonZeroUsize>,
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use data::Cache;
|
||||||
|
use config::Config;
|
||||||
|
|
||||||
|
/// Program state
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct State
|
||||||
|
{
|
||||||
|
config: Arc<Config>,
|
||||||
|
cache: Cache,
|
||||||
|
depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State
|
||||||
|
{
|
||||||
|
/// Create a new state
|
||||||
|
pub fn new(cfg: Config) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
config: Arc::new(cfg),
|
||||||
|
cache: Cache::new(),
|
||||||
|
depth: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current depth of this tree
|
||||||
|
pub fn depth(&self) -> usize
|
||||||
|
{
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone into increased depth, if config allows a deeper run.
|
||||||
|
pub fn deeper(&self) -> Option<Self>
|
||||||
|
{
|
||||||
|
if self.config.recursive.can_run(self.depth+1) {
|
||||||
|
Some(Self{
|
||||||
|
depth: self.depth + 1,
|
||||||
|
..self.clone()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The configuration for this run
|
||||||
|
pub fn config(&self) -> &Config
|
||||||
|
{
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reference to the cache of this run
|
||||||
|
pub fn cache(&self) -> &Cache
|
||||||
|
{
|
||||||
|
&self.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe to this state's cache
|
||||||
|
pub fn cache_sub(&self) -> Cache
|
||||||
|
{
|
||||||
|
self.cache.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to consume the state into the cache.
|
||||||
|
///
|
||||||
|
/// Fails if there are other references of this state alive.
|
||||||
|
pub fn try_into_cache(self) -> Result<Cache, Self>
|
||||||
|
{
|
||||||
|
match Arc::try_unwrap(self.config)
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(self.cache),
|
||||||
|
Err(config) => Err(Self{config, ..self}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,166 @@
|
|||||||
|
use super::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use futures::prelude::*;
|
||||||
|
use futures::future::join_all;
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use data::INode;
|
||||||
|
use data::FsInfo;
|
||||||
|
use state::State;
|
||||||
|
|
||||||
|
/// Join a root path onto this hashmap.
|
||||||
|
fn join_root<'a>(root: impl AsRef<Path> + 'a, map: HashMap<PathBuf, INode>) -> impl Iterator<Item=(PathBuf, INode)> + 'a
|
||||||
|
{
|
||||||
|
map.into_iter().map(move |(k, v)| (root.as_ref().join(k), v))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_entry(entry: &tokio::fs::DirEntry, parent: INode) -> io::Result<FsInfo>
|
||||||
|
{
|
||||||
|
let meta = entry.metadata().await?;
|
||||||
|
if meta.is_dir()
|
||||||
|
{
|
||||||
|
Ok(FsInfo::Directory)
|
||||||
|
} else if meta.is_file()
|
||||||
|
{
|
||||||
|
Ok(FsInfo::File(meta.len(), parent))
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "Unknown file type"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains a graph of all paths and inodes that were successfully stat
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct INodeInfoGraph
|
||||||
|
{
|
||||||
|
inodes: HashMap<INode, FsInfo>, // FsInfo `file` 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl INodeInfoGraph
|
||||||
|
{
|
||||||
|
//TODO: Order by largest size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk on all paths in this state, then return a joined map of all
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If there are any more held references to `state`.
|
||||||
|
pub async fn work_on_all(state: State) -> INodeInfoGraph
|
||||||
|
{
|
||||||
|
let comp_children = join_all(state.config().paths.iter().map(|path| {
|
||||||
|
let path = path.clone();
|
||||||
|
async {
|
||||||
|
match tokio::fs::symlink_metadata(&path).await {
|
||||||
|
Ok(meta) => {
|
||||||
|
let inode = meta.inode();
|
||||||
|
tokio::spawn(walk(state.clone(), path.clone(), inode)).await
|
||||||
|
.ok()
|
||||||
|
.map(move |res| (res, path))
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to stat root {:?}: {}", path, err);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})).await;
|
||||||
|
|
||||||
|
// All children have completed here. Unwrap cache
|
||||||
|
let ino_map = {
|
||||||
|
let cache = state.try_into_cache().unwrap();
|
||||||
|
cache.try_complete().await.unwrap()
|
||||||
|
};
|
||||||
|
let mut output = HashMap::with_capacity(ino_map.len());
|
||||||
|
|
||||||
|
for path_comp in comp_children
|
||||||
|
{
|
||||||
|
if let Some((res, root)) = path_comp
|
||||||
|
{
|
||||||
|
for (path, ino) in join_root(&root, res)
|
||||||
|
{
|
||||||
|
if let Some(_) = ino_map.get(&ino) {
|
||||||
|
output.insert(path, ino);
|
||||||
|
} else {
|
||||||
|
eprintln!("No ino entry for {:?} ({:?})", path, ino);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INodeInfoGraph {
|
||||||
|
inodes: ino_map,
|
||||||
|
paths: output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk this directory.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A *unjoined* map of relative paths and `INode`s inserted into the state's `Cache`.
|
||||||
|
/// The caller must join its `root` with these paths to provide a normalized map.
|
||||||
|
/// Recusrive calls to `walk` handle this automatically.
|
||||||
|
fn walk(state: State, root: PathBuf, root_ino: INode) -> BoxFuture<'static, HashMap<PathBuf, INode>>
|
||||||
|
{
|
||||||
|
let mut output = HashMap::new();
|
||||||
|
let mut children: Vec<JoinHandle<HashMap<PathBuf, INode>>> = Vec::new();
|
||||||
|
async move {
|
||||||
|
match fs::read_dir(&root).await
|
||||||
|
{
|
||||||
|
Ok(mut dir) => {
|
||||||
|
while let Some(entry) = dir.next().await
|
||||||
|
{
|
||||||
|
match entry
|
||||||
|
{
|
||||||
|
Ok(entry) => {
|
||||||
|
let ino = entry.inode();
|
||||||
|
// Check cache for this
|
||||||
|
if state.cache().get(&ino).await.is_none() {
|
||||||
|
// Not added, process.
|
||||||
|
match process_entry(&entry, root_ino).await {
|
||||||
|
Ok(fsinfo) => {
|
||||||
|
if fsinfo.is_dir()
|
||||||
|
{
|
||||||
|
if let Some(next) = state.deeper()
|
||||||
|
{
|
||||||
|
children.push(tokio::spawn(
|
||||||
|
walk(next, entry.path(), ino)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut cache = state.cache_sub();
|
||||||
|
cache.insert(ino, fsinfo).await;
|
||||||
|
},
|
||||||
|
Err(err) => eprintln!("Failed to stat {:?}: {}", entry.path(), err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => eprintln!("Walking {:?} failed: {}", root, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => eprintln!("Failed to walk {:?}: {}", root, err),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join all children
|
||||||
|
for child in join_all(children.into_iter()).await
|
||||||
|
{
|
||||||
|
if let Ok(map) = child
|
||||||
|
{
|
||||||
|
output.extend(join_root(&root, map));
|
||||||
|
} else {
|
||||||
|
eprintln!("Child panic");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue