use crate::config::{find_vyctor_root, load_config, VYCTOR_DIR};
use crate::daemon::{
format_uptime, get_daemon_status, is_daemon_running, log_file_path, read_log_tail,
start_daemon, stop_daemon,
};
use crate::indexer::{FileWalker, Indexer};
use anyhow::Result;
use colored::Colorize;
use notify::RecursiveMode;
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
use std::collections::HashSet;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::PathBuf;
use std::sync::mpsc::channel;
use std::time::Duration;
pub async fn run(
debounce_ms: u64,
daemon: bool,
stop: bool,
status: bool,
logs: bool,
follow: bool,
daemon_child: bool,
) -> Result<()> {
let root = find_vyctor_root()?;
if stop {
return run_stop(&root);
}
if status {
return run_status(&root);
}
if logs {
return run_logs(&root, follow);
}
if daemon {
return run_daemon(&root, debounce_ms);
}
run_foreground(&root, debounce_ms, daemon_child).await
}
fn run_daemon(root: &std::path::Path, debounce_ms: u64) -> Result<()> {
if let Some(pid) = is_daemon_running(root) {
println!("{} Watcher already running (PID {})", "→".cyan(), pid);
return Ok(());
}
println!("{} Starting watcher daemon...", "→".cyan());
match start_daemon(root, debounce_ms) {
Ok(pid) => {
println!("{} Watcher started (PID {})", "✓".green(), pid);
println!(" Log file: {}", log_file_path(root).display());
println!();
println!(" Run 'vyctor watch --status' to check status");
println!(" Run 'vyctor watch --logs -f' to follow logs");
println!(" Run 'vyctor watch --stop' to stop");
Ok(())
}
Err(e) => {
eprintln!("{} Failed to start daemon: {}", "!".red(), e);
Err(e)
}
}
}
fn run_stop(root: &std::path::Path) -> Result<()> {
match stop_daemon(root) {
Ok(true) => {
println!("{} Watcher stopped", "✓".green());
Ok(())
}
Ok(false) => {
println!("{} Watcher is not running", "→".cyan());
Ok(())
}
Err(e) => {
eprintln!("{} Failed to stop daemon: {}", "!".red(), e);
Err(e)
}
}
}
fn run_status(root: &std::path::Path) -> Result<()> {
let status = get_daemon_status(root);
println!("{} Watcher status for {}", "→".cyan(), root.display());
println!();
if status.running {
let pid = status
.pid
.expect("PID should be present when status.running is true");
println!(" Status: {}", "Running".green());
println!(" PID: {}", pid);
if let Some(started_at) = status.started_at {
println!(" Uptime: {}", format_uptime(started_at));
}
println!();
println!(" Log file: {}", log_file_path(root).display());
} else {
println!(" Status: {}", "Not running".yellow());
println!();
println!(" Run 'vyctor watch --daemon' to start");
}
Ok(())
}
fn run_logs(root: &std::path::Path, follow: bool) -> Result<()> {
let log_path = log_file_path(root);
if !log_path.exists() {
println!("{} No log file found", "→".cyan());
return Ok(());
}
if follow {
println!("{} Following logs (Ctrl+C to stop)...", "→".cyan());
println!();
let file = std::fs::File::open(&log_path)?;
let mut reader = BufReader::new(file);
reader.seek(SeekFrom::End(0))?;
let initial = read_log_tail(root, 20)?;
if !initial.is_empty() {
for line in initial.lines() {
println!("{}", line);
}
}
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => {
std::thread::sleep(Duration::from_millis(100));
}
Ok(_) => {
print!("{}", line);
}
Err(e) => {
eprintln!("{} Error reading log: {}", "!".red(), e);
break;
}
}
}
} else {
let content = read_log_tail(root, 50)?;
if content.is_empty() {
println!("{} Log file is empty", "→".cyan());
} else {
println!("{}", content);
}
}
Ok(())
}
async fn run_foreground(root: &std::path::Path, debounce_ms: u64, is_daemon: bool) -> Result<()> {
let config = load_config()?;
if is_daemon {
println!(
"[{}] Daemon started",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
);
println!(" Root: {}", root.display());
println!(" Debounce: {}ms", debounce_ms);
} else {
println!("{} Starting file watcher...", "→".cyan());
println!(" Root: {}", root.display());
println!(" Debounce: {}ms", debounce_ms);
println!();
println!(
"{}",
"Watching for file changes. Press Ctrl+C to stop.".dimmed()
);
println!();
}
let file_walker = FileWalker::new(
root.to_path_buf(),
config.indexing.include.clone(),
config.indexing.exclude.clone(),
);
let indexer = Indexer::new(root, &config)?;
let (tx, rx) = channel();
let mut debouncer = new_debouncer(Duration::from_millis(debounce_ms), move |res| {
if let Ok(events) = res {
let _ = tx.send(events);
}
})?;
debouncer.watcher().watch(root, RecursiveMode::Recursive)?;
loop {
match rx.recv() {
Ok(events) => {
let mut paths_to_index: HashSet<PathBuf> = HashSet::new();
let mut paths_to_remove: HashSet<PathBuf> = HashSet::new();
for event in events {
let path = &event.path;
if path.components().any(|c| c.as_os_str() == VYCTOR_DIR) {
continue;
}
if path.is_file() && !file_walker.should_index(path) {
continue;
}
match event.kind {
DebouncedEventKind::Any => {
if path.exists() && path.is_file() {
paths_to_index.insert(path.clone());
} else if !path.exists() {
paths_to_remove.insert(path.clone());
}
}
DebouncedEventKind::AnyContinuous => {
}
_ => {
}
}
}
let timestamp = if is_daemon {
format!("[{}] ", chrono::Local::now().format("%H:%M:%S"))
} else {
String::new()
};
for path in paths_to_remove {
let relative = path.strip_prefix(root).unwrap_or(&path).display();
match indexer.remove_file(&path) {
Ok(true) => {
if is_daemon {
println!("{}− {} (removed)", timestamp, relative);
} else {
println!(" {} {} (removed)", "−".red(), relative);
}
}
Ok(false) => {
}
Err(e) => {
if is_daemon {
println!("{}! Failed to remove {}: {}", timestamp, relative, e);
} else {
eprintln!(
" {} Failed to remove {}: {}",
"!".yellow(),
relative,
e
);
}
}
}
}
for path in paths_to_index {
let relative = path.strip_prefix(root).unwrap_or(&path).display();
match indexer.index_file(&path).await {
Ok(true) => {
if is_daemon {
println!("{}+ {} (indexed)", timestamp, relative);
} else {
println!(" {} {} (indexed)", "+".green(), relative);
}
}
Ok(false) => {
}
Err(e) => {
if is_daemon {
println!("{}! Failed to index {}: {}", timestamp, relative, e);
} else {
eprintln!(" {} Failed to index {}: {}", "!".yellow(), relative, e);
}
}
}
}
}
Err(e) => {
if is_daemon {
println!(
"[{}] Watcher error: {}",
chrono::Local::now().format("%H:%M:%S"),
e
);
} else {
eprintln!("{} Watcher error: {}", "!".red(), e);
}
}
}
}
}