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)
}
}