ffxiv-screenshot-organiser/src/main.rs

115 lines
2.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![feature(rust_2018_preview, iterator_find_map, use_extern_macros)]
#[macro_use] extern crate serde_derive;
mod config;
mod state;
use crate::{
config::Config,
state::State,
};
use chrono::{DateTime, Duration, Local, TimeZone, Utc};
use failure::Error;
use notify::{DebouncedEvent, RecursiveMode, Watcher};
use std::{fs, path::PathBuf, sync::mpsc};
pub type Result<T> = std::result::Result<T, Error>;
fn main() -> Result<()> {
let config_path = match std::env::args().nth(1) {
Some(x) => x,
None => "config.json".into(),
};
let f = fs::File::open(config_path)?;
let config: Config = serde_json::from_reader(f)?;
let screenshots_dir = config.options.screenshots_dir.canonicalize()?;
let (tx, rx) = mpsc::channel();
for entry in std::fs::read_dir(&screenshots_dir)? {
let entry = entry?;
if let Err(e) = handle(&config, entry.path()) {
eprintln!("{}", e);
}
}
let mut watcher = notify::watcher(
tx,
Duration::milliseconds(config.options.event_delay).to_std().unwrap(),
)?;
watcher.watch(
screenshots_dir.to_string_lossy().to_string(),
RecursiveMode::NonRecursive
)?;
loop {
match rx.recv() {
Ok(DebouncedEvent::Create(p)) => if let Err(e) = handle(&config, p) {
eprintln!("{}", e);
},
Ok(_) => {},
Err(e) => eprintln!("{:#?}", e),
}
}
}
fn handle(config: &Config, p: PathBuf) -> Result<()> {
let screenshots_dir = config.options.screenshots_dir.canonicalize()?;
// if the path doesn't exist, ignore
// no need to print out an error usually a result of duplicate events
if !p.exists() {
return Ok(());
}
// if the path isn't at the top level, ignore
if p.strip_prefix(&screenshots_dir).unwrap().components().count() != 1 {
return Ok(());
}
// ignore paths without file names
let file_name: String = match p.file_name() {
Some(f) => f.to_string_lossy().to_string(),
None => return Ok(()),
};
// ignore paths that don't match
let time = match parse_screenshot_name(&config, &file_name) {
Some(t) => t,
None => return Ok(()),
};
// execute the jobs in the pipeline
let mut state = State::new(p, time);
for job in &config.pipeline {
job.execute(&config, &mut state)?;
}
Ok(())
}
fn parse_screenshot_name(config: &Config, s: &str) -> Option<DateTime<Utc>> {
let caps = config.options.patterns.iter().find_map(|p| p.captures(s))?;
let dt = Local
.ymd(
caps.name("year")?.as_str().parse().ok()?,
caps.name("month")?.as_str().parse().ok()?,
caps.name("day")?.as_str().parse().ok()?,
)
.and_hms(
caps.name("hour")?.as_str().parse().ok()?,
caps.name("minute")?.as_str().parse().ok()?,
caps.name("second")?.as_str().parse().ok()?,
);
Some(dt.with_timezone(&Utc))
}