mod config;
mod error;
mod fs_notify;
mod library;
mod mime_type;
mod template;
use clap::{Args, Parser, Subcommand};
use std::collections::HashSet;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;
static GLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);
static GLOBAL_FAILED_TREADS: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Parser)]
#[clap(name = "fs-librarian")]
#[clap(about = "Goes through file types inside directories and does with them as you wish", long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
#[clap(arg_required_else_help = true)]
Watch {
config_path: String,
},
#[clap(arg_required_else_help = true)]
SingleShot {
config_path: String,
},
Test(Test),
}
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true)]
struct Test {
#[clap(subcommand)]
command: TestCommands,
}
#[derive(Debug, Subcommand)]
enum TestCommands {
Mime {
#[clap(value_parser)]
file_path: String,
},
}
fn main() {
let args = Cli::parse();
match args.command {
Commands::Watch { config_path } => {
watch(&config_path);
}
Commands::SingleShot { config_path } => {
single_shot(&config_path);
}
Commands::Test(t) => {
test(&t);
}
}
}
fn get_config(config_path: &String) -> config::Config {
match config::Config::new(config_path) {
Ok(c) => c,
Err(e) => {
eprintln!("{}", e);
std::process::exit(exitcode::CONFIG);
}
}
}
fn watch(config_path: &String) {
let conf = get_config(config_path);
let mut paths: HashSet<String> = HashSet::new();
for cur_lib_config in conf.libraries {
for cur_dir in cur_lib_config.1.filter.directories {
paths.insert(cur_dir);
}
}
let (on_event_sender, on_event_receiver) = channel();
let (mut notify_obj, _) =
fs_notify::Notify::new(&conf.fs_watch, &paths, on_event_sender).unwrap();
let config_path_clone = config_path.clone();
thread::spawn(move || loop {
let path = match on_event_receiver.recv() {
Ok(p) => p,
Err(e) => {
eprintln!("{}", e);
continue;
}
};
let conf = get_config(&config_path_clone);
for cur_lib_config in conf.libraries {
let cur_lib = library::Library::new(&cur_lib_config.1);
if !cur_lib.contains_path(Path::new(&path)) {
continue;
}
let number = match cur_lib.process(Some(Path::new(&path))) {
Ok(n) => n,
Err(e) => {
eprintln!("{}", e);
0
}
};
if number > 0 {
println!(
"Processed '{}' as part of the {} library",
path, cur_lib_config.0
);
}
}
});
single_shot(config_path);
notify_obj.watch();
}
fn single_shot(config_path: &String) {
let conf = get_config(config_path);
for cur_conf in conf.libraries {
GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
thread::spawn(move || {
match library::Library::new(&cur_conf.1).process(None) {
Ok(k) => {
println!("Processed {} files in the {} library", k, cur_conf.0);
}
Err(e) => {
eprintln!("{}", e);
GLOBAL_FAILED_TREADS.fetch_add(1, Ordering::SeqCst);
}
}
GLOBAL_THREAD_COUNT.fetch_sub(1, Ordering::SeqCst);
});
}
while GLOBAL_THREAD_COUNT.load(Ordering::SeqCst) != 0 {
thread::sleep(Duration::from_millis(1));
}
if GLOBAL_FAILED_TREADS.load(Ordering::SeqCst) > 0 {
std::process::exit(exitcode::DATAERR);
}
}
fn test(test: &Test) {
match &test.command {
TestCommands::Mime { file_path } => mime(file_path),
}
}
fn mime(file_path: &str) {
match mime_type::File::new(Path::new(file_path)).get_mime_type() {
Err(e) => {
eprintln!("{}", e);
std::process::exit(exitcode::DATAERR);
}
Ok(m) => {
println!("{}", m)
}
};
}