cargo-cache 0.8.3

Manage cargo cache ($CARGO_HOME or ~/.cargo/), show sizes and remove directories selectively
// Copyright 2017-2020 Matthias Krüger. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fs;
use std::path::Path;

use rayon::iter::*;
use walkdir::WalkDir;

#[allow(dead_code)] // only used in tests
#[allow(clippy::branches_sharing_code)]
pub(crate) fn bin_path() -> String {
    let target_dir = cargo_metadata::MetadataCommand::new()
        .exec()
        .unwrap()
        .target_directory;

    // check if we have a release or debug binary to run tests with
    // we need to take into account that linux and windows have different paths to the executable
    let path_release = if cfg!(windows) {
        let mut td = target_dir.clone();
        td.push("release");
        td.push("cargo-cache.exe");
        td
    } else {
        let mut td = target_dir.clone();
        td.push("release");
        td.push("cargo-cache");
        td
    };

    let path_debug = if cfg!(windows) {
        let mut td = target_dir;
        td.push("debug");
        td.push("cargo-cache.exe");
        td
    } else {
        let mut td = target_dir;
        td.push("debug");
        td.push("cargo-cache");
        td
    };

    if path_release.is_file() {
        path_release.into_string()
    } else if path_debug.is_file() {
        path_debug.into_string()
    } else {
        panic!("No cargo-cache executable found!");
    }
}

#[allow(dead_code)] // only used in tests
pub(crate) fn assert_path_end(path: &Path, wanted_vector: &[&str]) {
    // because windows and linux represent paths differently ( /foo/bar vs C:\\foo\\bar)
    // we need to take this into account when running test on windows/linux

    // the function just splits up paths into their individual components
    // and checks these against a vector string
    let components = path.components().map(|c| c.as_os_str().to_str().unwrap());
    let components_vec: Vec<&str> = components.collect();
    let wanted_len = components_vec.len() - wanted_vector.len();

    let is: &[&str] = &components_vec[wanted_len..];

    assert_eq!(is, wanted_vector);
}

#[allow(dead_code)] // only used in tests

/// get the total size and number of files of a directory
pub(crate) fn dir_size(dir: &Path) -> u64 {
    // Note: using a hashmap to cache dirsizes does apparently not pay out performance-wise
    if !dir.is_dir() {
        return 0;
    }

    // traverse recursively and sum filesizes, parallelized by rayon
    let walkdir_start = dir.display().to_string();

    let dir_size = WalkDir::new(&walkdir_start)
        .into_iter()
        .map(|e| e.unwrap().path().to_owned())
        .filter(|f| f.exists()) // avoid broken symlinks
        .collect::<Vec<_>>() // @TODO perhaps WalkDir will impl ParallelIterator one day
        .par_iter()
        .filter(|f| f.exists()) // check if the file still exists. Since collecting and processing a
        // path, some time may have passed and if we have a "cargo build" operation
        // running in the directory, a temporary file may be gone already and failing to unwrap() (#43)
        .map(|f| {
            fs::metadata(f)
                .unwrap_or_else(|_| panic!("Failed to get metadata of file '{}'", &f.display()))
                .len()
        })
        .sum();

    dir_size
}