use std::process::{Command, Child};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use notify::{Watcher, RecursiveMode, Event, event::{ModifyKind, AccessKind}};
use colored::*;
use std::path::Path;
use std::thread;
pub fn start_dev_server() -> Result<(), Box<dyn std::error::Error>> {
println!("{}", "Starting Oxidite development server...".green().bold());
println!("{}", "Watching for file changes...".cyan());
let child_process: Arc<Mutex<Option<Child>>> = Arc::new(Mutex::new(None));
restart_process(&child_process)?;
let child_clone = child_process.clone();
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = notify::recommended_watcher(move |res| {
if tx.send(res).is_err() {
}
})?;
watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
let mut last_restart = Instant::now();
let debounce_duration = Duration::from_millis(300);
for res in rx {
match res {
Ok(event) => {
if should_reload(&event) {
let now = Instant::now();
if now.duration_since(last_restart) > debounce_duration {
stop_process(&child_clone)?;
println!("\n{}", "Changes detected, restarting server...".yellow());
thread::sleep(Duration::from_millis(100));
restart_process(&child_clone)?;
last_restart = now;
}
}
},
Err(e) => println!("Watch error: {:?}", e),
}
}
Ok(())
}
fn stop_process(child_lock: &Arc<Mutex<Option<Child>>>) -> Result<(), Box<dyn std::error::Error>> {
let mut lock = child_lock.lock().unwrap();
if let Some(mut child) = lock.take() {
let _ = child.kill();
let _ = child.wait();
}
Ok(())
}
fn restart_process(child_lock: &Arc<Mutex<Option<Child>>>) -> Result<(), Box<dyn std::error::Error>> {
let mut lock = child_lock.lock().unwrap();
if let Some(mut child) = lock.take() {
let _ = child.kill();
let _ = child.wait();
}
let child = Command::new("cargo")
.arg("run")
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.spawn();
match child {
Ok(c) => {
*lock = Some(c);
Ok(())
},
Err(e) => {
println!("{} {}", "Failed to start server:".red(), e);
Err(Box::new(e))
}
}
}
fn should_reload(event: &Event) -> bool {
for path in &event.paths {
let path_str = path.to_string_lossy();
if path_str.contains("/target/")
|| path_str.starts_with("./target/")
|| path_str.contains("\\target\\")
|| path_str.starts_with(".\\target\\")
|| path_str.contains("/node_modules/")
|| path_str.contains("\\node_modules\\")
|| path_str.starts_with(".") && !path_str.starts_with("./") && !path_str.starts_with(".\\")
{
return false;
}
match &event.kind {
notify::EventKind::Modify(ModifyKind::Data(_)) |
notify::EventKind::Create(_) |
notify::EventKind::Remove(_) => {},
_ => return false,
}
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy();
if matches!(ext_str.as_ref(), "rs" | "toml" | "html" | "css" | "js" | "sql" | "md" | "yaml" | "yml" | "json" | "env") {
return true;
}
}
}
false
}