From 05123a5989dcb5b3538f941a8c101e1e002e0d08 Mon Sep 17 00:00:00 2001 From: Breadway Date: Mon, 11 May 2026 16:40:49 +0800 Subject: [PATCH] Enhance timestamp formatting and add reload watcher functionality --- bread-cli/Cargo.toml | 1 + bread-cli/src/main.rs | 50 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/bread-cli/Cargo.toml b/bread-cli/Cargo.toml index 6d2541e..5f18678 100644 --- a/bread-cli/Cargo.toml +++ b/bread-cli/Cargo.toml @@ -11,3 +11,4 @@ tokio.workspace = true anyhow.workspace = true clap = { version = "4.5", features = ["derive"] } notify = "6.1" +libc = "0.2" diff --git a/bread-cli/src/main.rs b/bread-cli/src/main.rs index 6e1982a..9382494 100644 --- a/bread-cli/src/main.rs +++ b/bread-cli/src/main.rs @@ -296,12 +296,22 @@ fn print_event(event: &Value, fields: Option<&str>) { fn format_timestamp(ms: u64) -> String { let secs = ms / 1000; let millis = ms % 1000; - let time = UNIX_EPOCH + Duration::from_secs(secs); - let datetime = time.duration_since(UNIX_EPOCH).unwrap_or_default(); - let seconds = datetime.as_secs() % 60; - let minutes = (datetime.as_secs() / 60) % 60; - let hours = (datetime.as_secs() / 3600) % 24; - format!("{:02}:{:02}:{:02}.{:03}", hours, minutes, seconds, millis) + + // SAFETY: localtime_r is thread-safe. We pass a valid pointer to a + // zeroed tm struct and read the result only after the call returns. + let local_secs = unsafe { + let mut tm: libc::tm = std::mem::zeroed(); + let t = secs as libc::time_t; + libc::localtime_r(&t, &mut tm); + tm.tm_hour as u64 * 3600 + + tm.tm_min as u64 * 60 + + tm.tm_sec as u64 + }; + + let h = (local_secs / 3600) % 24; + let m = (local_secs / 60) % 60; + let s = local_secs % 60; + format!("{:02}:{:02}:{:02}.{:03}", h, m, s, millis) } fn print_reload(value: &Value) { @@ -321,6 +331,18 @@ fn print_reload(value: &Value) { } } +async fn watch_reload(socket: &Path) -> Result<()> { + let config_dir = config_directory(); + println!("watching {} for changes...", config_dir.display()); + + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut watcher: RecommendedWatcher = notify::recommended_watcher(move |res| { + let _ = tx.send(res); + })?; + watcher.watch(&config_dir, RecursiveMode::Recursive)?; + + use tokio::time::{sleep, Duration}; + async fn watch_reload(socket: &Path) -> Result<()> { let config_dir = config_directory(); println!("watching {} for changes...", config_dir.display()); @@ -332,15 +354,25 @@ async fn watch_reload(socket: &Path) -> Result<()> { watcher.watch(&config_dir, RecursiveMode::Recursive)?; while let Some(msg) = rx.recv().await { - if msg.is_ok() { - let response = send_request(socket, "modules.reload", json!({})).await?; - print_reload(&response); + if msg.is_err() { + continue; } + + // Debounce: drain any follow-up events that arrive within 150ms. + // A single file save typically generates 2-3 fs events in rapid succession. + sleep(Duration::from_millis(150)).await; + while rx.try_recv().is_ok() {} + + let response = send_request(socket, "modules.reload", json!({})).await?; + print_reload(&response); } Ok(()) } + Ok(()) +} + async fn print_doctor(socket: &Path) -> Result<()> { let stream = match UnixStream::connect(socket).await { Ok(stream) => stream,