//! Utility functions and types for the geek-szitman-supercamera crate use std::time::{Duration, Instant}; use tracing::info; /// Signal handler for graceful shutdown pub struct SignalHandler { shutdown_requested: std::sync::Arc, } impl SignalHandler { /// Create a new signal handler pub fn new() -> Result> { let shutdown_requested = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let shutdown_clone = shutdown_requested.clone(); ctrlc::set_handler(move || { info!("Received shutdown signal"); shutdown_clone.store(true, std::sync::atomic::Ordering::SeqCst); })?; Ok(Self { shutdown_requested }) } /// Check if shutdown was requested pub fn shutdown_requested(&self) -> bool { self.shutdown_requested .load(std::sync::atomic::Ordering::SeqCst) } /// Wait for shutdown signal pub fn wait_for_shutdown(&self) { while !self.shutdown_requested() { std::thread::sleep(Duration::from_millis(100)); } } /// Request shutdown programmatically pub fn request_shutdown(&self) { self.shutdown_requested .store(true, std::sync::atomic::Ordering::SeqCst); } /// Wait for shutdown signal with timeout. Returns true if shutdown was requested, false if timed out. pub fn wait_for_shutdown_with_timeout(&self, timeout: Duration) -> bool { let start = Instant::now(); while !self.shutdown_requested() { if start.elapsed() >= timeout { return false; // timed out } std::thread::sleep(Duration::from_millis(100)); } true } } /// Performance metrics tracker pub struct PerformanceTracker { start_time: Instant, frame_count: u64, total_bytes: u64, last_fps_update: Instant, current_fps: f64, } impl PerformanceTracker { /// Create a new performance tracker pub fn new() -> Self { Self { start_time: Instant::now(), frame_count: 0, total_bytes: 0, last_fps_update: Instant::now(), current_fps: 0.0, } } /// Record a frame pub fn record_frame(&mut self, bytes: usize) { self.frame_count += 1; self.total_bytes += bytes as u64; // Update FPS every second let now = Instant::now(); if now.duration_since(self.last_fps_update) >= Duration::from_secs(1) { let elapsed = now.duration_since(self.last_fps_update).as_secs_f64(); self.current_fps = 1.0 / elapsed; self.last_fps_update = now; } } /// Get current FPS pub fn current_fps(&self) -> f64 { self.current_fps } /// Get total frame count pub fn total_frames(&self) -> u64 { self.frame_count } /// Get total bytes processed pub fn total_bytes(&self) -> u64 { self.total_bytes } /// Get average frame size pub fn average_frame_size(&self) -> f64 { if self.frame_count > 0 { self.total_bytes as f64 / self.frame_count as f64 } else { 0.0 } } /// Get uptime pub fn uptime(&self) -> Duration { self.start_time.elapsed() } /// Get performance summary pub fn summary(&self) -> PerformanceSummary { PerformanceSummary { uptime: self.uptime(), total_frames: self.total_frames(), total_bytes: self.total_bytes(), current_fps: self.current_fps(), average_frame_size: self.average_frame_size(), } } } impl Default for PerformanceTracker { fn default() -> Self { Self::new() } } /// Performance summary #[derive(Debug, Clone)] pub struct PerformanceSummary { pub uptime: Duration, pub total_frames: u64, pub total_bytes: u64, pub current_fps: f64, pub average_frame_size: f64, } impl PerformanceSummary { /// Format uptime as human-readable string pub fn uptime_formatted(&self) -> String { let secs = self.uptime.as_secs(); let hours = secs / 3600; let minutes = (secs % 3600) / 60; let seconds = secs % 60; if hours > 0 { format!("{hours}h {minutes}m {seconds}s") } else if minutes > 0 { format!("{minutes}m {seconds}s") } else { format!("{seconds}s") } } /// Format bytes as human-readable string pub fn bytes_formatted(&self) -> String { const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; let mut size = self.total_bytes as f64; let mut unit_index = 0; while size >= 1024.0 && unit_index < UNITS.len() - 1 { size /= 1024.0; unit_index += 1; } format!("{:.1} {}", size, UNITS[unit_index]) } } /// Configuration file loader pub struct ConfigLoader; impl ConfigLoader { /// Load configuration from file pub fn load_from_file(path: &str) -> Result> where T: serde::de::DeserializeOwned, { let content = std::fs::read_to_string(path)?; let config: T = serde_json::from_str(&content)?; Ok(config) } /// Save configuration to file pub fn save_to_file(path: &str, config: &T) -> Result<(), Box> where T: serde::Serialize, { let content = serde_json::to_string_pretty(config)?; std::fs::write(path, content)?; Ok(()) } /// Load configuration with fallback to defaults pub fn load_with_defaults(path: &str) -> T where T: serde::de::DeserializeOwned + Default, { Self::load_from_file(path).unwrap_or_default() } } /// File utilities pub struct FileUtils; impl FileUtils { /// Ensure directory exists pub fn ensure_dir(path: &str) -> Result<(), Box> { std::fs::create_dir_all(path)?; Ok(()) } /// Get file size pub fn get_file_size(path: &str) -> Result> { let metadata = std::fs::metadata(path)?; Ok(metadata.len()) } /// Check if file exists pub fn file_exists(path: &str) -> bool { std::path::Path::new(path).exists() } /// Get file extension pub fn get_extension(path: &str) -> Option { std::path::Path::new(path) .extension() .and_then(|ext| ext.to_str()) .map(|s| s.to_string()) } } /// Time utilities pub struct TimeUtils; impl TimeUtils { /// Format duration as human-readable string pub fn format_duration(duration: Duration) -> String { let secs = duration.as_secs(); let millis = duration.subsec_millis(); if secs > 0 { format!("{secs}.{millis:03}s") } else { format!("{millis}ms") } } /// Sleep with progress callback pub fn sleep_with_progress(duration: Duration, mut progress_callback: F) where F: FnMut(f64), { let start = Instant::now(); let total_duration = duration.as_millis() as f64; while start.elapsed() < duration { let elapsed = start.elapsed().as_millis() as f64; let progress = (elapsed / total_duration).min(1.0); progress_callback(progress); std::thread::sleep(Duration::from_millis(10)); } progress_callback(1.0); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_performance_tracker() { let mut tracker = PerformanceTracker::new(); // Record some frames tracker.record_frame(1024); tracker.record_frame(2048); tracker.record_frame(1536); assert_eq!(tracker.total_frames(), 3); assert_eq!(tracker.total_bytes(), 4608); assert_eq!(tracker.average_frame_size(), 1536.0); } #[test] fn test_performance_summary() { let mut tracker = PerformanceTracker::new(); tracker.record_frame(1024); let summary = tracker.summary(); assert_eq!(summary.total_frames, 1); assert_eq!(summary.total_bytes, 1024); assert_eq!(summary.average_frame_size, 1024.0); // Test formatting assert!(summary.uptime_formatted().contains("s")); assert_eq!(summary.bytes_formatted(), "1.0 KB"); } #[test] fn test_file_utils() { // Test file existence check assert!(FileUtils::file_exists("src/utils/mod.rs")); assert!(!FileUtils::file_exists("nonexistent_file.txt")); // Test extension extraction assert_eq!( FileUtils::get_extension("test.txt"), Some("txt".to_string()) ); assert_eq!(FileUtils::get_extension("no_extension"), None); } #[test] fn test_time_utils() { let duration = Duration::from_millis(1500); let formatted = TimeUtils::format_duration(duration); assert!(formatted.contains("1.500s")); let short_duration = Duration::from_millis(500); let formatted = TimeUtils::format_duration(short_duration); assert!(formatted.contains("500ms")); } #[test] fn test_sleep_with_progress() { let mut progress_values = Vec::new(); let duration = Duration::from_millis(100); TimeUtils::sleep_with_progress(duration, |progress| { progress_values.push(progress); }); assert!(!progress_values.is_empty()); assert!(progress_values.last().unwrap() >= &1.0); } }