use crate::commands::files::remote::RemoteClient;
use crate::commands::files::state::{SyncState, state_dir};
use crate::commands::files::sync_engine::{Conflict, reconcile};
use anyhow::Result;
use notify::{Event, RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
#[allow(clippy::too_many_arguments)]
pub async fn run(
api_url: &str,
api_key: &str,
org_id: Option<&str>,
quiet: bool,
session_id: String,
local_dir: String,
interval: u64,
conflict: String,
exclude: Vec<String>,
no_gitignore: bool,
dry_run: bool,
delete: bool,
verbose: bool,
) -> Result<()> {
let local_dir = PathBuf::from(&local_dir).canonicalize()?;
let client = RemoteClient::new_with_org(api_url, api_key, &session_id, org_id);
let conflict_strategy = Conflict::parse(&conflict);
let sd = state_dir(&local_dir);
let mut state = SyncState::load(&sd, &session_id)?;
if !quiet {
eprintln!(
"Syncing {} ↔ session {} (poll {}s, conflict: {})",
local_dir.display(),
session_id,
interval,
conflict,
);
if dry_run {
eprintln!("(dry-run mode)");
}
}
if !quiet {
eprintln!("Initial sync...");
}
let stats = reconcile(
&client,
&local_dir,
&mut state,
conflict_strategy,
no_gitignore,
&exclude,
dry_run,
delete,
verbose,
)
.await?;
if !quiet {
eprintln!("Initial sync done: {}", stats);
}
let local_changed = Arc::new(AtomicBool::new(false));
let changed_flag = local_changed.clone();
let local_dir_clone = local_dir.clone();
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
if let Ok(event) = res {
let dominated_by_sync = event
.paths
.iter()
.all(|p| p.starts_with(local_dir_clone.join(".everruns-sync")));
if !dominated_by_sync {
changed_flag.store(true, Ordering::Relaxed);
}
}
})?;
watcher.watch(&local_dir, RecursiveMode::Recursive)?;
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.ok();
r.store(false, Ordering::Relaxed);
});
let poll_dur = Duration::from_secs(interval);
let mut total_up: u64 = 0;
let mut total_down: u64 = 0;
while running.load(Ordering::Relaxed) {
tokio::time::sleep(poll_dur).await;
if !running.load(Ordering::Relaxed) {
break;
}
let _local_dirty = local_changed.swap(false, Ordering::Relaxed);
let stats = reconcile(
&client,
&local_dir,
&mut state,
conflict_strategy,
no_gitignore,
&exclude,
dry_run,
delete,
verbose,
)
.await;
match stats {
Ok(s) => {
total_up += s.uploaded as u64;
total_down += s.downloaded as u64;
let has_activity =
s.uploaded > 0 || s.downloaded > 0 || s.conflicts > 0 || s.errors > 0;
if has_activity && !quiet {
eprintln!("{}", s);
}
}
Err(e) => {
eprintln!("Sync error: {}", e);
}
}
}
if !quiet {
eprintln!("\nSync stopped. Total: ↑{} ↓{}", total_up, total_down);
}
Ok(())
}