use clap::{Parser, ValueEnum};
use std::fs;
use std::io;
use std::{path::Path, path::PathBuf};
use std::sync::{Arc, Mutex};
use rayon::prelude::*;
mod exif;
mod exif_error;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const COMMIT: &str = env!("GIT_COMMIT_HASH");
const BUILD_DATE: &str = env!("BUILD_DATE");
const AUTHOR : &str = env!("CARGO_PKG_AUTHORS");
const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
fn long_version() -> &'static str {
Box::leak(
format!(
"\nVersion: {}\nCommit: {}\nBuild Date: {}",
VERSION, COMMIT, BUILD_DATE
)
.into_boxed_str()
)
}
const SUPPORTED_EXTENSIONS: [&str; 2] = ["jpg", "jpeg"];
const DEFAULT_PATTERN: &str = "%Y%m%d_%H%M%S";
#[derive(Clone, ValueEnum)]
enum Cmd {
ExifToFilename,
FilenameToExif,
}
#[derive(Parser)]
#[command(
author = AUTHOR,
version = long_version(),
about = DESCRIPTION,
long_version = long_version()
)]
struct Cli {
command: Cmd,
#[arg(short = 'p', long, required = true)]
path: String,
#[arg(short = 'P', long, default_value = DEFAULT_PATTERN)]
pattern: Option<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let directory = Path::new(&cli.path);
if !directory.is_dir() {
println!("Error: Invalid directory path");
return Ok(());
}
let files: Vec<_> = fs::read_dir(directory)?
.filter_map(|entry: Result<fs::DirEntry, io::Error>| {
let entry: fs::DirEntry = entry.ok()?;
let binding: PathBuf = entry.path();
let file_ext: &str = binding.extension()?.to_str()?;
if SUPPORTED_EXTENSIONS.contains(&file_ext.to_lowercase().as_str()) {
Some(entry)
} else {
None
}
})
.collect();
let total: usize = files.len();
let pattern: String = cli.pattern.unwrap_or_else(|| DEFAULT_PATTERN.to_string());
let mutex: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
let process_files = |process_fn: fn(&Path, &str)| {
files.into_par_iter().for_each(|file: fs::DirEntry| {
let mut progress = mutex.lock().unwrap();
*progress += 1;
println!("\nProcessing: {}/{}\n", *progress, total);
let path: PathBuf = file.path();
println!("Processing file: {}", file.path().display());
process_fn(&path, &pattern);
});
};
match cli.command {
Cmd::ExifToFilename => process_files(
|path: &Path, pattern: &str| exif::exif_to_filename(path, pattern, path.extension().unwrap())
),
Cmd::FilenameToExif => process_files(
|path: &Path, pattern: &str| exif::filename_to_exif(path, pattern)
),
}
Ok(())
}