mod config; mod remote; mod cli; mod init; extern crate notify; use notify::{Watcher, RecursiveMode, watcher, DebouncedEvent}; use std::sync::mpsc::{channel, Sender}; use std::time::Duration; use crate::config::SessionConfig; use crate::cli::SubCommand; use crate::cli::CliOptions; use structopt::StructOpt; use std::path::Path; use std::thread; use std::sync::{Mutex, Arc}; use std::vec::Vec; use crate::remote::Remote; use std::thread::sleep; // Perform rsync from source to destination fn sync(config: &config::SessionConfig) { use std::process::Command; fn rsync(source: &str, destinatin: &str, args: &Vec) { println!("executing rsync: {} {}", source, destinatin); let mut rsync = Command::new("rsync") .arg("-v") // verbose output .arg("-a") // archived: we use this to only sync files which have changed .arg("-r") // recursive .args(args) .arg(source) .arg(destinatin) .spawn() .expect("failed to execute rsync"); let result = rsync.wait().expect("failed to wait for process"); println!("rsync finished"); assert!(result.success()); } // we sync actions explicitly here, since they might be ignored otherwise let dirsync_dir_local = &format!("{}/.dirsync", &config.local_root); let dirsync_dir_remote = &format!("{}", &config.destination()); rsync(dirsync_dir_local, dirsync_dir_remote, &Vec::new()); let exclude_gitignore = config.ignore_gitignore && Path::new(".gitignore").exists(); let exclude_file = Path::new(config.exclude_path().to_str().unwrap()).exists(); let mut args: Vec = Vec::new(); if exclude_gitignore { args.push( String::from("--exclude-from=.gitignore") ); } if exclude_file { args.push( String::from( format!("--exclude-from={}", config.exclude_path().to_str().unwrap()) ) ); } rsync(&config.local_root, &config.destination(), &args) } fn filter(event: DebouncedEvent) -> Option { match event { DebouncedEvent::NoticeWrite(_) => None, DebouncedEvent::NoticeRemove(_) => None, DebouncedEvent::Rescan => None, DebouncedEvent::Error(_, _) => None, _ => Some(event) } } fn start_watch_thread(root: String, flush_signal: Sender<()>, events: &mut Arc>>) { let events = Arc::clone(events); thread::spawn(move|| { // Create a channel to receive watcher events. let (tx, rx) = channel(); let mut watcher = watcher(tx, Duration::from_millis(20)).unwrap(); watcher.watch(root, RecursiveMode::Recursive).unwrap(); loop { match rx.recv() { Ok(event) => { println!("handling event: {:?}", event); match filter(event) { Some(event) => { let signal = flush_signal.clone(); let mut events_vec = events.lock().unwrap(); events_vec.push(event); thread::spawn(move|| { sleep(Duration::from_millis(20)); signal.send(()).unwrap(); }); }, None => println!("ignoring event") }; }, Err(e) => println!("watch error: {:?}", e), } } }); } fn flush_events(config: &SessionConfig, remote: &mut Remote, events: &mut Arc>>) { let mut events_vec = events.lock().unwrap(); if !events_vec.is_empty() { events_vec.clear(); sync(&config); println!("Executing onSyncDidFinish action"); remote.execute_if_exists("onSyncDidFinish"); } } fn start_main_loop(config: &SessionConfig) { println!("config: {:?}", config); sync(&config); let mut remote = Remote::connect(config); remote.execute_if_exists("onSessionDidStart"); let mut events: Arc>> = Arc::new(Mutex::new(vec![])); // create a channel for flush events let (tx, rx) = channel(); start_watch_thread(config.local_root.clone(), tx, &mut events); loop { let _ = rx.recv(); flush_events(&config, &mut remote, &mut events); } } fn main() { let opts = CliOptions::from_args(); match opts.subcommand { Some(SubCommand::Init(remote_config)) => init::init_dirsync_dir(remote_config).unwrap(), Some(SubCommand::Clean) => { let config = SessionConfig::get(opts); let mut remote = remote::Remote::connect(&config); remote.remove_dir(config.remote.root.as_str()); }, _ => { let config = SessionConfig::get(opts); start_main_loop(&config); } }; }