fwatchd 0.1.0

fwatchd - A file watching daemon
mod socket;
use anyhow::{anyhow, Context, Result};
use clap::{Arg, ArgMatches, Command};
use socket::*;
use std::io::prelude::*;
use std::os::unix::net::UnixStream;

fn build() -> clap::Command<'static> {
    let mut app = Command::new("fwatchd - A file watching daemon")
        .version("2021")
        .author("Patrik Lundgren <patrik.lundgren.95@gmail.com>")
        .about("fwatch utilizes the inotity API to watch files \n");

    app = app.subcommand(
        Command::new("track")
            .about("track file, take snapshot when changed.")
            .arg(Arg::new("file").required(true).takes_value(true))
            .arg(
                Arg::new("script")
                    .long("script")
                    .value_name("SCRIPT")
                    .takes_value(true)
                    .help("command which is executed on events"),
            )
            .arg(
                Arg::new("alias")
                    .long("alias")
                    .value_name("ALIAS")
                    .takes_value(true)
                    .help("Script called to determine an alias for a file given an event"),
            ),
    );

    app = app.subcommand(
        Command::new("list")
            .about("list available file snapshots")
            .arg(Arg::new("file").takes_value(true)),
    );

    app = app.subcommand(
        Command::new("select")
            .about("list available file snapshots")
            .arg(Arg::new("file").takes_value(true))
            .arg(Arg::new("hash").takes_value(true)),
    );

    app = app.subcommand(
        Command::new("echo")
            .about("Test unix socket for daemon")
            .arg(Arg::new("message").takes_value(true)),
    );

    app = app.subcommand(
        Command::new("echoerr")
            .about("Test unix socket for daemon")
            .arg(Arg::new("message").takes_value(true)),
    );

    app
}

fn track(args: &ArgMatches) -> Result<()> {
    let fpath: String = args.value_of_t("file").context("No pattern provided")?;
    let action = match args.value_of_t::<String>("script") {
        Ok(path) => Action::Script(path),
        _ => Action::Save,
    };
    let alias: Alias = match args.value_of_t::<String>("alias") {
        Ok(spath) => Alias::Script(spath),
        _ => Alias::Basename,
    };
    let track = Track {
        fpath,
        alias,
        action,
    };
    let payload = bincode::serialize(&track).context("Failed to serialize payload")?;
    let mut stream = UnixStream::connect(SOCK_PATH)?;
    let mut response = String::new();

    let pkt = Packet {
        command: socket::Command::Track,
        payload,
    };
    stream.write_all(&bincode::serialize(&pkt)?)?;
    stream.read_to_string(&mut response)?;
    println!("{}", response);
    Ok(())
}

fn list(args: &ArgMatches) -> Result<()> {
    let payload: String = args
        .value_of_t("file")
        .ok()
        .or_else(|| Some("*".to_string()))
        .context("No pattern provided")?;

    let mut stream = UnixStream::connect(SOCK_PATH)?;
    let mut response = String::new();
    let payload = bincode::serialize(&payload).context("Failed to serialize payload")?;
    let pkt = Packet {
        command: socket::Command::List,
        payload,
    };

    stream.write_all(&bincode::serialize(&pkt)?)?;
    stream.read_to_string(&mut response)?;
    println!("{}", response);
    Ok(())
}

fn select(args: &ArgMatches) -> Result<()> {
    let sel: (String, String) = (
        args.value_of_t("file").context("No pattern provided")?,
        args.value_of_t("hash").context("No pattern provided")?,
    );
    let mut stream = UnixStream::connect(SOCK_PATH).context("Failed to open socket")?;
    let mut response = String::new();
    let payload = bincode::serialize(&sel).context("Failed to serialize payload")?;
    let pkt = Packet {
        command: socket::Command::Select,
        payload,
    };

    stream
        .write_all(&bincode::serialize(&pkt)?)
        .context("Failed to write to socket")?;
    stream
        .read_to_string(&mut response)
        .context("Failed to read from socket")?;

    println!("{}", response);
    Ok(())
}

fn echo(args: &ArgMatches, is_err: bool) -> Result<()> {
    let msg: String = args
        .value_of_t("message")
        .ok()
        .context("No message provided")?;
    let mut stream = UnixStream::connect(SOCK_PATH)?;
    let mut response = String::new();
    let payload = bincode::serialize(&msg).context("Failed to serialize payload")?;
    let pkt = Packet {
        command: if is_err {
            socket::Command::Echoerr
        } else {
            socket::Command::Echo
        },
        payload,
    };

    stream.write_all(&bincode::serialize(&pkt)?)?;
    stream.read_to_string(&mut response)?;
    println!("{}", response);
    Ok(())
}

fn dispatch(app: &mut Command, matches: &ArgMatches) {
    if let Err(msg) = match matches.subcommand() {
        Some(("track", sargs)) => track(sargs),
        Some(("list", sargs)) => list(sargs),
        Some(("select", sargs)) => select(sargs),
        Some(("echo", sargs)) => echo(sargs, false),
        Some(("echoerr", sargs)) => echo(sargs, true),
        None => {
            println!("{}", app.render_usage());
            Ok(())
        }
        _ => Err(anyhow!("Unrecognized command")),
    } {
        println!("{}", msg);
    }
}

fn main() {
    let mut app = build();
    let matches = app.clone().try_get_matches();
    match matches {
        Ok(m) => dispatch(&mut app, &m),
        Err(msg) => println!("{}", msg),
    };
}