dsc 0.1.3

dsc is a cli tool for finding and removing duplicate files on one or multiple file systems, while respecting your gitignore rules.
use std::ops::Div;
use std::path::PathBuf;
use std::thread::JoinHandle;

use anyhow::{anyhow, Result};
use crossbeam::channel;
use crossbeam::channel::Sender;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};

use crate::candidate_selection::coordinator::Coordinator;
use crate::candidate_selection::messages::AnalysisMessage;
use crate::options::Options;
use crate::types::Duplicate;
use crate::ui;

pub struct CandidateSelector {
    options: Options,
}

impl CandidateSelector {
    pub fn new(options: Options) -> Self {
        CandidateSelector { options }
    }

    pub fn select_candidates(&self, path_vec: &[PathBuf]) -> Result<Vec<Duplicate>> {
        info!("Creating coordinator");
        let coordinator = Coordinator::new(self.options);

        info!("Creating progress display");

        let (sender, display_handle) = if self.options.debug {
            self.spawn_progress_debug()
        } else if self.options.no_progress {
            self.spawn_progress_noop()
        } else {
            self.spawn_progress_display()
        };

        info!("Awaiting coordination completion");

        let res = coordinator.coordinate(path_vec, sender);

        display_handle
            .join()
            .map_err(|_| anyhow!("Could not join coordinator thread"))??;

        res
    }

    fn spawn_progress_noop(&self) -> (Sender<AnalysisMessage>, JoinHandle<Result<()>>) {
        let (sender, receiver) = channel::unbounded();

        let handle = std::thread::spawn(move || {
            while receiver.recv().is_ok() {}
            Ok(())
        });

        (sender, handle)
    }

    fn spawn_progress_debug(&self) -> (Sender<AnalysisMessage>, JoinHandle<Result<()>>) {
        let (sender, receiver) = channel::unbounded();

        let handle = std::thread::spawn(move || {
            debug!("Starting debug analysis log");
            while let Ok(msg) = receiver.recv() {
                debug!("{:?}", msg);
            }
            debug!("Finished debug analysis log");
            Ok(())
        });

        (sender, handle)
    }

    fn spawn_progress_display(&self) -> (Sender<AnalysisMessage>, JoinHandle<Result<()>>) {
        let (sender, receiver) = channel::unbounded();

        let multi = MultiProgress::new();

        let main_style = ProgressStyle::default_spinner()
            .template("[{elapsed_precise:.yellow}] {percent:.green.bright}{prefix:.green.bright} of data is duplicated ({msg:.yellow} speedup)");

        let file_style = ProgressStyle::default_spinner()
            .template("|{spinner:.white.dim}| Files [duplicates/total]: {pos}/{len:} {msg}")
            .tick_strings(ui::TURBO_FISH);

        let bytes_style = ProgressStyle::default_spinner()
            .template("           Size  [duplicated/total]: {bytes}/{total_bytes} {msg}");

        let main = multi.add(ProgressBar::new(0));
        main.set_style(main_style);
        main.set_prefix("%");

        let files = multi.add(ProgressBar::new(0));
        files.set_style(file_style);
        files.enable_steady_tick(50);

        let bytes = multi.add(ProgressBar::new(0));

        bytes.set_style(bytes_style);

        let progress_updater = std::thread::spawn(move || {
            while let Ok(msg) = receiver.recv() {
                let stats = match msg {
                    AnalysisMessage::Update(stats) => stats,
                    AnalysisMessage::Finished(stats) => stats,
                };

                let mut speedup =
                    (stats.total_bytes_encountered as f64).div(stats.total_bytes_read as f64);

                if speedup.is_nan() {
                    speedup = 1.0
                }

                main.set_length(stats.total_bytes_encountered);
                main.set_position(stats.total_bytes_duplicated);
                main.set_message(&format!("×{:.2}", speedup));

                files.set_length(stats.files_found);
                files.set_position(stats.duplicates_found);

                bytes.set_length(stats.total_bytes_encountered);
                bytes.set_position(stats.total_bytes_duplicated);
            }

            main.finish_and_clear();
            files.finish_and_clear();
            bytes.finish_and_clear();
        });

        let handle = std::thread::spawn(move || {
            multi
                .join()
                .map_err(|_| anyhow!("Failed to properly close display"))?;
            progress_updater
                .join()
                .map_err(|_| anyhow!("Failed to properly close display"))
        });

        (sender, handle)
    }
}