use anyhow::Context; use std::path::{Path, PathBuf}; use structopt::StructOpt; use summain::ManifestEntry; use walkdir::WalkDir; #[tokio::main] async fn main() -> anyhow::Result<()> { let opt = Opt::from_args(); let mut files = vec![]; for pathname in opt.pathnames { find(&pathname, &mut files)?; } files.sort(); // Get metadata about all files, but don't compute checksum yet. // // This all runs in async worker threads, since it's theoretically // I/O heavy and benefits from async task context switching. We // create a task for each named file, since tasks are very cheap. // We keep the JoinHandle for each task in a vector: there's not // going to be so many handles that the vector becomes impossibly // large: Linux only allows 128 KiB of command line arguments. let mut handles = vec![]; for filename in files.iter().cloned() { handles.push(tokio::spawn(async move { manifest(filename).await })); } // Compute checksums for regular files. // // This runs in blocking threads, since it's CPU heavy. We create // another vector of JoinHandles. Again, it won't become too large. let mut sumhandles = vec![]; for h in handles { let mut m: ManifestEntry = h.await??; let h = tokio::task::spawn_blocking(move || match m.compute_checksum() { Err(e) => Err(e), Ok(_) => Ok(m), }); sumhandles.push(h) } // Wait for checksums to be available and print manifest. // // Note how this iterates over the results in the right order. for h in sumhandles { let m: ManifestEntry = h.await??; print!("{}", serde_yaml::to_string(&m)?); } Ok(()) } fn find(root: &Path, files: &mut Vec) -> anyhow::Result<()> { let meta = std::fs::symlink_metadata(root)?; if meta.file_type().is_symlink() { // Walkdir doesn't handle dangling symlinks so we handle all // symlinks here. files.push(root.to_path_buf()); return Ok(()); } for e in WalkDir::new(root) { files.push(e?.path().to_path_buf()); } Ok(()) } #[derive(StructOpt, Debug)] struct Opt { #[structopt(parse(from_os_str))] pathnames: Vec, } async fn manifest(path: PathBuf) -> anyhow::Result { ManifestEntry::new(&path) .await .with_context(|| format!("{}", path.display())) }