//! `warpgate status` — show service status, cache stats, write-back queue, bandwidth. use anyhow::Result; use crate::config::Config; use crate::rclone::{mount, rc}; pub fn run(config: &Config) -> Result<()> { // Check mount status for each share let mut any_mounted = false; for share in &config.shares { let mounted = match mount::is_mounted(&share.mount_point) { Ok(m) => m, Err(e) => { eprintln!("Warning: could not check mount for '{}': {}", share.name, e); false } }; let ro_tag = if share.read_only { " (ro)" } else { "" }; if mounted { println!( "Mount: UP {} → {}{}", share.mount_point.display(), share.name, ro_tag ); any_mounted = true; } else { println!("Mount: DOWN {}{}", share.name, ro_tag); } } if !any_mounted { println!("\nNo rclone VFS mounts are active."); println!("Start with: systemctl start warpgate"); return Ok(()); } // Aggregate stats from all share RC ports let mut total_bytes = 0u64; let mut total_speed = 0.0f64; let mut total_transfers = 0u64; let mut total_errors = 0u64; let mut total_cache_used = 0u64; let mut total_uploading = 0u64; let mut total_queued = 0u64; let mut total_errored = 0u64; let mut rc_reachable = false; for (i, _share) in config.shares.iter().enumerate() { let port = config.rc_port(i); if let Ok(stats) = rc::core_stats(port) { rc_reachable = true; total_bytes += stats.bytes; total_speed += stats.speed; total_transfers += stats.transfers; total_errors += stats.errors; } if let Ok(vfs) = rc::vfs_stats(port) { if let Some(dc) = vfs.disk_cache { total_cache_used += dc.bytes_used; total_uploading += dc.uploads_in_progress; total_queued += dc.uploads_queued; total_errored += dc.errored_files; } } } if rc_reachable { println!("Speed: {}/s", format_bytes(total_speed as u64)); println!("Moved: {}", format_bytes(total_bytes)); println!("Active: {} transfers", total_transfers); println!("Errors: {}", total_errors); println!("Cache: {}", format_bytes(total_cache_used)); println!( "Dirty: {} uploading, {} queued", total_uploading, total_queued ); if total_errored > 0 { println!("Errored: {} files", total_errored); } } else { eprintln!("Could not reach any rclone RC API."); } Ok(()) } fn format_bytes(bytes: u64) -> String { const KIB: f64 = 1024.0; const MIB: f64 = KIB * 1024.0; const GIB: f64 = MIB * 1024.0; let b = bytes as f64; if b >= GIB { format!("{:.1} GiB", b / GIB) } else if b >= MIB { format!("{:.1} MiB", b / MIB) } else if b >= KIB { format!("{:.1} KiB", b / KIB) } else { format!("{} B", bytes) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_format_bytes_zero() { assert_eq!(format_bytes(0), "0 B"); } #[test] fn test_format_bytes_bytes() { assert_eq!(format_bytes(512), "512 B"); } #[test] fn test_format_bytes_kib() { assert_eq!(format_bytes(1024), "1.0 KiB"); assert_eq!(format_bytes(1536), "1.5 KiB"); } #[test] fn test_format_bytes_mib() { assert_eq!(format_bytes(1048576), "1.0 MiB"); } #[test] fn test_format_bytes_gib() { assert_eq!(format_bytes(1073741824), "1.0 GiB"); } #[test] fn test_format_bytes_boundary() { assert_eq!(format_bytes(1023), "1023 B"); assert_eq!(format_bytes(1024), "1.0 KiB"); } }