rmx 0.1.6

Faster GNU 'rm' drop in replacement with extra features
use std::borrow::ToOwned;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::ops::Range;
use std::path::{Path, PathBuf};

use rand::Rng;
use zeroize::Zeroize;

use crate::Result;

const DEFAULT_SHRED_ITERATIONS: usize = 1000;

pub struct Shredder {
    path: PathBuf,
    name: OsString,
    size_range: Range<usize>,
}

#[allow(clippy::cast_possible_truncation)]
pub fn shred(path: &OsStr) -> Result<()> {
    let size = fs::metadata(path)?.len() as usize;
    if size == 0 {
        fs::remove_file(&path)?;
        return Ok(());
    }

    Shredder::new(path, size).run()
}

impl Shredder {
    pub fn new(path: &OsStr, size: usize) -> Self {
        let size_range = (size - size / 2)..(size + size / 2);
        let name = Path::new(path)
            .file_name()
            .map(ToOwned::to_owned)
            .unwrap_or_default();

        Self {
            path: PathBuf::from(path),
            name,
            size_range,
        }
    }

    fn run(&mut self) -> Result<()> {
        let instruction: usize = rand::thread_rng().gen_range(1..=3000);

        for _ in 1..=DEFAULT_SHRED_ITERATIONS {
            match instruction {
                0..=1000 => self.noise()?,
                1001..=2000 => self.write()?,
                2001..=3000 => self.rename()?,
                _ => (),
            }
        }

        fs::remove_file(&self.path)?;

        Ok(())
    }

    fn noise(&self) -> Result<()> {
        let size: usize = rand::thread_rng().gen_range(self.size_range.clone());
        let mut bytes: Vec<u8> = (1..=size).map(|_| rand::random::<u8>()).collect();
        let name: String = rand::thread_rng()
            .sample_iter(&rand::distributions::Alphanumeric)
            .take(30)
            .map(char::from)
            .collect();

        fs::write(&name, &bytes)?;
        fs::remove_file(&name)?;

        bytes.zeroize();

        Ok(())
    }

    fn write(&self) -> Result<()> {
        let size: usize = rand::thread_rng().gen_range(self.size_range.clone());
        let mut bytes: Vec<u8> = (1..=size).map(|_| rand::random::<u8>()).collect();

        fs::write(&self.path, &bytes)?;

        bytes.zeroize();

        Ok(())
    }

    fn rename(&mut self) -> Result<()> {
        let name: OsString = OsString::from(
            rand::thread_rng()
                .sample_iter(&rand::distributions::Alphanumeric)
                .take(30)
                .map(char::from)
                .collect::<String>(),
        );
        let parent = self
            .path
            .clone()
            .parent()
            .map(ToOwned::to_owned)
            .unwrap_or_default();
        let path = parent.join(&name);

        fs::rename(&self.path, &path)?;
        self.path = path;
        self.name = name;

        Ok(())
    }
}