summaryrefslogtreecommitdiff
path: root/src/utils/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/mod.rs')
-rw-r--r--src/utils/mod.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
new file mode 100644
index 0000000..b89357e
--- /dev/null
+++ b/src/utils/mod.rs
@@ -0,0 +1,350 @@
+//! 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<std::sync::atomic::AtomicBool>,
+}
+
+impl SignalHandler {
+ /// Create a new signal handler
+ pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
+ 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<T>(path: &str) -> Result<T, Box<dyn std::error::Error>>
+ 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<T>(path: &str, config: &T) -> Result<(), Box<dyn std::error::Error>>
+ 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<T>(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<dyn std::error::Error>> {
+ std::fs::create_dir_all(path)?;
+ Ok(())
+ }
+
+ /// Get file size
+ pub fn get_file_size(path: &str) -> Result<u64, Box<dyn std::error::Error>> {
+ 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<String> {
+ 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<F>(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);
+ }
+}