cargo-reclaim 0.2.2

Safe Cargo cleanup for target directories, stale artifacts, and Cargo home caches
Documentation
use std::fs;
use std::io;
use std::path::Path;

use super::model::{CargoHomePathKind, CargoHomeProblem};

pub(super) struct MeasuredPath {
    pub kind: CargoHomePathKind,
    pub size_bytes: u64,
    pub skipped: bool,
    pub reason: String,
    pub problems: Vec<CargoHomeProblem>,
}

pub(super) fn measure_existing_path(path: &Path) -> io::Result<MeasuredPath> {
    let metadata = fs::symlink_metadata(path)?;
    let file_type = metadata.file_type();
    if file_type.is_symlink() {
        return Ok(MeasuredPath {
            kind: CargoHomePathKind::Symlink,
            size_bytes: 0,
            skipped: true,
            reason: "preserved; symlink was not followed".to_string(),
            problems: Vec::new(),
        });
    }

    if file_type.is_file() {
        return Ok(MeasuredPath {
            kind: CargoHomePathKind::File,
            size_bytes: metadata.len(),
            skipped: false,
            reason: "preserved; Cargo home report is read-only".to_string(),
            problems: Vec::new(),
        });
    }

    if file_type.is_dir() {
        let mut problems = Vec::new();
        let size_bytes = measure_directory(path, &mut problems);
        let skipped = !problems.is_empty();
        let reason = if skipped {
            "preserved; one or more child paths could not be read".to_string()
        } else {
            "preserved; Cargo home report is read-only".to_string()
        };
        return Ok(MeasuredPath {
            kind: CargoHomePathKind::Directory,
            size_bytes,
            skipped,
            reason,
            problems,
        });
    }

    Ok(MeasuredPath {
        kind: CargoHomePathKind::Other,
        size_bytes: metadata.len(),
        skipped: false,
        reason: "preserved; unrecognized filesystem entry".to_string(),
        problems: Vec::new(),
    })
}

fn measure_directory(path: &Path, problems: &mut Vec<CargoHomeProblem>) -> u64 {
    let children = match fs::read_dir(path) {
        Ok(children) => children,
        Err(error) => {
            problems.push(CargoHomeProblem {
                path: path.to_path_buf(),
                message: error.to_string(),
            });
            return 0;
        }
    };

    let mut total = 0u64;
    for child in children {
        let child = match child {
            Ok(child) => child,
            Err(error) => {
                problems.push(CargoHomeProblem {
                    path: path.to_path_buf(),
                    message: error.to_string(),
                });
                continue;
            }
        };
        total = total.saturating_add(measure_child(&child.path(), problems));
    }
    total
}

fn measure_child(path: &Path, problems: &mut Vec<CargoHomeProblem>) -> u64 {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(error) => {
            problems.push(CargoHomeProblem {
                path: path.to_path_buf(),
                message: error.to_string(),
            });
            return 0;
        }
    };
    let file_type = metadata.file_type();
    if file_type.is_symlink() {
        return 0;
    }
    if file_type.is_dir() {
        return measure_directory(path, problems);
    }
    metadata.len()
}