diff --git a/rust/src/app.rs b/rust/src/app.rs index f2ea8f0..e38c0bb 100644 --- a/rust/src/app.rs +++ b/rust/src/app.rs @@ -27,6 +27,8 @@ pub enum InstallerState { #[derive(Default)] pub struct DownloadState { pub progress: f32, + pub speed: f64, + pub estimated_remaining: std::time::Duration, pub completed: bool, pub files: Option>, pub error: Option, @@ -38,6 +40,8 @@ pub struct SinfarInstallerApp<> { pub install_path: Option, pub ee_exe_path: Option, pub download_progress: f32, + pub download_speed: f64, + pub estimated_remaining: std::time::Duration, pub extraction_progress: f32, pub download_error: Option, pub install_error: Option, @@ -47,13 +51,15 @@ pub struct SinfarInstallerApp<> { } impl SinfarInstallerApp<> { - pub fn new(cc: &eframe::CreationContext<>, runtime: Arc) -> Self { + pub fn new(_cc: &eframe::CreationContext<>, runtime: Arc) -> Self { Self { state: InstallerState::GameSelection, game_type: Some(GameType::Diamond), install_path: None, ee_exe_path: None, download_progress: 0.0, + download_speed: 0.0, + estimated_remaining: std::time::Duration::new(0, 0), extraction_progress: 0.0, download_error: None, install_error: None, @@ -91,14 +97,8 @@ impl<> eframe::App for SinfarInstallerApp<> { if self.state == InstallerState::Download { if let Ok(state) = self.download_state.lock() { self.download_progress = state.progress; - - // if state.completed { - // if let Some(files) = &state.files { - // // Store the files for installation - // //self.next_state(); // Proceed to installation - // //self.start_installation(); - // } - // } + self.download_speed = state.speed; + self.estimated_remaining = state.estimated_remaining; if let Some(error) = &state.error { self.download_error = Some(error.clone()); @@ -178,12 +178,10 @@ impl<> eframe::App for SinfarInstallerApp<> { // Implementation of key installer functions impl SinfarInstallerApp<> { pub fn start_download(&mut self) { - - // let temp_dir = std::env::temp_dir().join("sinfar_installer"); - // println!("{}", temp_dir.display()) - // Reset progress and errors self.download_progress = 0.0; + self.download_speed = 0.0; + self.estimated_remaining = std::time::Duration::from_secs(0); self.download_error = None; // Initialize download state @@ -203,10 +201,12 @@ impl SinfarInstallerApp<> { // Create a download manager with progress callback let progress_state = download_state.clone(); - let manager = DownloadManager::new(move |progress, _file| { + let manager = DownloadManager::new(move |progress, _file, speed, remaining| { // Update progress through shared state if let Ok(mut state) = progress_state.lock() { state.progress = progress; + state.speed = speed; + state.estimated_remaining = remaining; repaint_ctx_progress.request_repaint(); } }); diff --git a/rust/src/installer/downloader.rs b/rust/src/installer/downloader.rs index 0458aaf..e2b77cd 100644 --- a/rust/src/installer/downloader.rs +++ b/rust/src/installer/downloader.rs @@ -9,20 +9,19 @@ use futures_util::StreamExt; use tokio::sync::Mutex; use std::fs::metadata; use reqwest::header::{HeaderMap, HeaderValue, RANGE, USER_AGENT}; -use log::{info, warn, error}; -use tokio::fs::File; -use crate::app::{GameType}; +use log::{info, warn}; +use crate::app::GameType; pub struct DownloadManager { client: Client, - progress_callback: Arc>>, + progress_callback: Arc>>, temp_dir: PathBuf, } impl DownloadManager { pub fn new(progress_callback: F) -> Self where - F: Fn(f32, &str) + Send + Sync + 'static + F: Fn(f32, &str, f64, Duration) + Send + Sync + 'static { let client = Client::builder() .timeout(Duration::from_secs(1800)) @@ -44,6 +43,13 @@ impl DownloadManager { temp_dir, } } + + pub fn set_progress_callback(&mut self, callback: F) + where + F: Fn(f32, &str, f64, Duration) + Send + Sync + 'static, + { + self.progress_callback = Arc::new(Mutex::new(Box::new(callback))); + } /// Downloads a file with resume capability and progress tracking pub async fn download_file(&self, url: &str, output_path: &Path) -> Result<()> { @@ -126,6 +132,8 @@ impl DownloadManager { .unwrap_or("file"); let mut last_update = std::time::Instant::now(); + let mut last_downloaded_size = downloaded_size; + let start_time = std::time::Instant::now(); while let Some(chunk_result) = stream.next().await { let chunk = chunk_result?; @@ -138,10 +146,25 @@ impl DownloadManager { if now.duration_since(last_update) > Duration::from_millis(100) || content_length == downloaded_size { if content_length > 0 { let progress = downloaded_size as f32 / content_length as f32; + + // Calculate speed in bytes per second + let elapsed = now.duration_since(last_update).as_secs_f64(); + let bytes_since_last = downloaded_size - last_downloaded_size; + let speed = bytes_since_last as f64 / elapsed; + + // Calculate estimated time remaining + let remaining_bytes = content_length - downloaded_size; + let estimated_remaining = if speed > 0.0 { + Duration::from_secs_f64(remaining_bytes as f64 / speed) + } else { + Duration::from_secs(0) + }; + let callback = progress_callback.lock().await; - callback(progress, filename); + callback(progress, filename, speed, estimated_remaining); } last_update = now; + last_downloaded_size = downloaded_size; } } @@ -166,7 +189,7 @@ impl DownloadManager { } else { // File exists, update progress to 100% let callback = self.progress_callback.lock().await; - callback(1.0, "sinfar_all_files_v30.7z"); + callback(1.0, "sinfar_all_files_v30.7z", 0.0, Duration::from_secs(0)); } downloaded_files.push(content_zip_path); @@ -182,7 +205,7 @@ impl DownloadManager { ).await?; } else { let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarx.exe"); + callback(1.0, "sinfarx.exe", 0.0, Duration::from_secs(0)); } downloaded_files.push(launcher_path); @@ -199,7 +222,7 @@ impl DownloadManager { ).await?; } else { let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarLauncher.zip"); + callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0)); } downloaded_files.push(launcher_zip_path); @@ -216,7 +239,7 @@ impl DownloadManager { ).await?; } else { let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarLauncher.zip"); + callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0)); } downloaded_files.push(launcher_zip_path); diff --git a/rust/src/installer/mod.rs b/rust/src/installer/mod.rs index cd7efc6..4994122 100644 --- a/rust/src/installer/mod.rs +++ b/rust/src/installer/mod.rs @@ -1,5 +1,4 @@ pub mod downloader; -pub mod downloaderv2; pub mod extractor; pub mod validator; pub mod shortcut; \ No newline at end of file diff --git a/rust/src/ui/download_progress.rs b/rust/src/ui/download_progress.rs index 3597244..0ad9e6e 100644 --- a/rust/src/ui/download_progress.rs +++ b/rust/src/ui/download_progress.rs @@ -1,14 +1,47 @@ pub mod download_progress { use eframe::egui::{Ui, ProgressBar}; use crate::app::SinfarInstallerApp; + use std::time::Duration; + + fn format_bytes(bytes: f64) -> String { + const KB: f64 = 1024.0; + const MB: f64 = KB * 1024.0; + + if bytes >= MB { + format!("{:.1} MB/s", bytes / MB) + } else if bytes >= KB { + format!("{:.1} KB/s", bytes / KB) + } else { + format!("{:.0} B/s", bytes) + } + } + + fn format_duration(duration: Duration) -> String { + let secs = duration.as_secs(); + if secs >= 3600 { + format!("{:02}:{:02}:{:02} remaining", secs / 3600, (secs % 3600) / 60, secs % 60) + } else { + format!("{:02}:{:02} remaining", secs / 60, secs % 60) + } + } pub fn render(ui: &mut Ui, app: &mut SinfarInstallerApp) { ui.heading("Downloading Sinfar Custom Content"); ui.add_space(10.0); - ui.horizontal(|ui| { - ui.label("Downloading:"); - ui.add(ProgressBar::new(app.download_progress).show_percentage()); + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.label("Downloading:"); + ui.add(ProgressBar::new(app.download_progress).show_percentage()); + }); + + if app.download_progress > 0.0 && app.download_progress < 1.0 { + ui.horizontal(|ui| { + ui.label(format_bytes(app.download_speed)); + ui.label(" • "); + ui.label(format_duration(app.estimated_remaining)); + }); + } }); if let Some(error) = &app.download_error {