cargo-reclaim 0.2.2

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

use crate::{ReclaimError, ReclaimResult};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiskFreeSpace {
    pub available_bytes: u64,
    pub total_bytes: u64,
}

impl DiskFreeSpace {
    pub fn free_basis_points(self) -> Option<u16> {
        if self.total_bytes == 0 {
            return None;
        }
        let basis_points =
            (u128::from(self.available_bytes) * 10_000) / u128::from(self.total_bytes);
        u16::try_from(basis_points.min(10_000)).ok()
    }
}

pub fn disk_free_space(path: impl AsRef<Path>) -> ReclaimResult<DiskFreeSpace> {
    let path = path.as_ref();
    let available_bytes = fs2::available_space(path).map_err(|error| ReclaimError::DiskRead {
        path: path.to_path_buf(),
        message: format!("failed to measure available disk space: {error}"),
    })?;
    let total_bytes = fs2::total_space(path).map_err(|error| ReclaimError::DiskRead {
        path: path.to_path_buf(),
        message: format!("failed to measure total disk space: {error}"),
    })?;
    Ok(DiskFreeSpace {
        available_bytes,
        total_bytes,
    })
}

pub fn disk_free_basis_points(path: impl AsRef<Path>) -> ReclaimResult<Option<u16>> {
    Ok(disk_free_space(path)?.free_basis_points())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn computes_basis_points_with_integer_floor() {
        assert_eq!(
            DiskFreeSpace {
                available_bytes: 1,
                total_bytes: 4
            }
            .free_basis_points(),
            Some(2_500)
        );
    }

    #[test]
    fn ignores_zero_total_space() {
        assert_eq!(
            DiskFreeSpace {
                available_bytes: 1,
                total_bytes: 0
            }
            .free_basis_points(),
            None
        );
    }
}