mod dupes;
mod join;
mod list;
mod probe;
mod rebuild;
mod rename;
use crate::entries::Entry;
use crate::fetcher::{DirPolicy, FetcherArgs, InputInfo};
use crate::{error, utils};
use anyhow::Result;
use clap::{Args, Subcommand};
#[derive(Debug, Args)]
pub struct CommandArgs<T: Args> {
#[command(flatten)]
cmd: T,
#[command(flatten)]
args: FetcherArgs,
#[command(flatten)]
mods: Modifiers,
}
#[derive(Debug, Args)]
#[command(next_help_heading = Some("Modifiers"))]
pub struct Modifiers {
#[arg(long)]
pub show: bool,
#[arg(long)]
pub full: bool,
}
#[derive(Debug, Subcommand)]
pub enum RefineCommand {
#[command(override_usage = "refine dupes [DIRS]... [FETCH] [OPTIONS]")]
Dupes(CommandArgs<dupes::Dupes>),
#[command(override_usage = "refine join [DIRS]... [FETCH] [OPTIONS]")]
Join(CommandArgs<join::Join>),
#[command(override_usage = "refine list [DIRS]... [FETCH] [OPTIONS]")]
List(CommandArgs<list::List>),
#[command(override_usage = "refine rebuild [DIRS]... [FETCH] [OPTIONS]")]
Rebuild(CommandArgs<rebuild::Rebuild>),
#[command(override_usage = "refine rename [DIRS]... [FETCH] [OPTIONS]")]
Rename(CommandArgs<rename::Rename>),
#[command(override_usage = "refine probe [DIRS]... [FETCH] [OPTIONS]")]
Probe(CommandArgs<probe::Probe>),
}
pub trait Refine {
type Media: TryFrom<Entry, Error = (Entry, anyhow::Error)>;
const OPENING_LINE: &'static str;
const DIR_POLICY: DirPolicy;
fn tweak(&mut self, _: &InputInfo) {}
fn peek(&mut self, _: &[Self::Media]) {}
fn refine(&self, medias: Vec<Self::Media>) -> Result<()>;
}
impl RefineCommand {
pub fn run(self) -> Result<()> {
match self {
RefineCommand::Dupes(cmd_args) => run(cmd_args),
RefineCommand::Join(cmd_args) => run(cmd_args),
RefineCommand::List(cmd_args) => run(cmd_args),
RefineCommand::Rebuild(cmd_args) => run(cmd_args),
RefineCommand::Rename(cmd_args) => run(cmd_args),
RefineCommand::Probe(cmd_args) => run(cmd_args),
}
}
}
fn run<R: Refine + Args>(ca: CommandArgs<R>) -> Result<()> {
println!("{}\n", R::OPENING_LINE);
let (fetcher, info) = ca.args.try_into()?; if !ca.mods.full {
Entry::prefix_len(fetcher.find_common_dir_prefix_length()); }
let iter: Box<dyn Iterator<Item = Entry>> = if ca.mods.show {
println!("Entries this command will process:");
let mut entries = fetcher.fetch(R::DIR_POLICY).collect::<Vec<_>>();
entries.sort_unstable_by(|e, f| utils::natural_cmp(e.to_str(), f.to_str()));
entries.iter().for_each(|e| println!("{e}"));
if entries.is_empty() {
println!("no entries found");
return Ok(());
}
println!("\ntotal entries: {}", entries.len());
utils::prompt_yes_no("Do you want to continue?")?;
println!();
Box::new(entries.into_iter())
} else {
Box::new(fetcher.fetch(R::DIR_POLICY))
};
let mut cmd = ca.cmd;
cmd.tweak(&info);
let medias = gen_medias(iter);
cmd.peek(&medias);
cmd.refine(medias)
}
fn gen_medias<T>(entries: impl Iterator<Item = Entry>) -> Vec<T>
where
T: TryFrom<Entry, Error = (Entry, anyhow::Error)>,
{
entries
.map(|entry| T::try_from(entry))
.inspect(|res| {
if let Err((entry, err)) = res {
error!("load media {entry}: {err}");
}
})
.flatten()
.collect()
}