From 9bc602c1364ee436975cf63d672012eceaf39d76 Mon Sep 17 00:00:00 2001 From: Fiery Imp Date: Wed, 23 Apr 2025 20:37:19 -0400 Subject: [PATCH] Claude: Fine tuned download progress screen --- rust/src/app.rs | 8 +- rust/src/installer/downloader.rs | 189 ++++++++++++++++++------------- rust/src/ui/download_progress.rs | 22 ++-- 3 files changed, 131 insertions(+), 88 deletions(-) diff --git a/rust/src/app.rs b/rust/src/app.rs index cb88f6e..362b29e 100644 --- a/rust/src/app.rs +++ b/rust/src/app.rs @@ -97,8 +97,12 @@ impl<> eframe::App for SinfarInstallerApp<> { if self.state == InstallerState::Download { if let Ok(state) = self.download_state.lock() { - // Only update progress if not cancelled - if !state.cancelled { + // Set progress to 100% if completed + if state.completed { + self.download_progress = 1.0; + self.download_speed = 0.0; + self.estimated_remaining = std::time::Duration::from_secs(0); + } else if !state.cancelled { self.download_progress = state.progress; self.download_speed = state.speed; self.estimated_remaining = state.estimated_remaining; diff --git a/rust/src/installer/downloader.rs b/rust/src/installer/downloader.rs index e2b77cd..d0e9dff 100644 --- a/rust/src/installer/downloader.rs +++ b/rust/src/installer/downloader.rs @@ -62,65 +62,119 @@ impl DownloadManager { } } - // Check if we can resume a previous download + + // Prepare headers for potential resume let mut headers = HeaderMap::new(); headers.insert(USER_AGENT, HeaderValue::from_static("Sinfar NWN Installer")); + // First make a HEAD request to get the total file size + let response_head = self.client.get(url) + .headers(headers) + .send() + .await + .context("Failed to send HTTP request")?; - let mut downloaded_size: u64 = 0; - - if output_path.exists() { - if let Ok(metadata) = tokio::fs::metadata(output_path).await { - downloaded_size = metadata.len(); - if downloaded_size > 0 { - info!("Resuming download from byte {}", downloaded_size); - headers.insert(RANGE, HeaderValue::from_str(&format!("bytes={}-", downloaded_size))?); - } - } - } - - let response = self.client.get(url) - .headers(headers) - .send() - .await - .context("Failed to send HTTP request")?; + let status = response_head.status(); - // Check if the server supports resuming - let can_resume = response.status().is_success() && response.status().as_u16() == 206; - let status = response.status(); - - if !status.is_success() { - return Err(anyhow!("Server returned error status: {}", status)); - } - - // Get total file size - let content_length = if can_resume { - if let Some(content_range) = response.headers().get("content-range") { + // Get content length for progress calculation + let total_size = response_head.content_length().unwrap_or(0); + let content_length = if status.as_u16() == 206 { + if let Some(content_range) = response_head.headers().get("content-range") { let range_str = content_range.to_str().unwrap_or(""); if let Some(size_part) = range_str.split('/').nth(1) { size_part.parse::().unwrap_or(0) } else { - response.content_length().unwrap_or(0) + downloaded_size + total_size } } else { - response.content_length().unwrap_or(0) + downloaded_size + total_size } } else { - response.content_length().unwrap_or(0) + response_head.content_length().unwrap_or(total_size) }; - info!("Download size: {} bytes", content_length); + // Check if we have an existing file and its size + let mut downloaded_size: u64 = 0; + if output_path.exists() { + let metadata = tokio::fs::metadata(output_path).await?; + downloaded_size = metadata.len(); + + // If the file size matches exactly, we're done + if content_length > 0 && downloaded_size == content_length { + info!("File is already completely downloaded: {} bytes", content_length); + // Call progress callback with 100% + let progress_callback = self.progress_callback.clone(); + let filename = output_path.file_name() + .and_then(|f| f.to_str()) + .unwrap_or("file"); + let callback = progress_callback.lock().await; + callback(1.0, filename, 0.0, Duration::from_secs(0)); + return Ok(()); + } else if downloaded_size > content_length { + // If local file is larger than remote, start fresh + info!("Local file size mismatch ({} > {}), starting fresh download", downloaded_size, content_length); + downloaded_size = 0; + } + } - // Open file in appropriate mode (append if resuming, create/truncate if starting fresh) - let mut file = if can_resume && downloaded_size > 0 { - info!("Opening file for append"); + // Prepare headers for potential resume + let mut headers_toresume = HeaderMap::new(); + headers_toresume.insert(USER_AGENT, HeaderValue::from_static("Sinfar NWN Installer")); + + if downloaded_size > 0 { + info!("Attempting to resume from byte {}", downloaded_size); + headers_toresume.insert(RANGE, HeaderValue::from_str(&format!("bytes={}-", downloaded_size))?); + } + + //Make the actual download request + let response = self.client.get(url) + .headers(headers_toresume) + .send() + .await + .context("Failed to send HTTP request")?; + + let status = response.status(); + + // Handle 416 Range Not Satisfiable specifically + if status.as_u16() == 416 { + // This can happen if the file is already complete + if downloaded_size > 0 && downloaded_size == total_size { + info!("File appears to be complete, got 416 with matching file size"); + let progress_callback = self.progress_callback.clone(); + let filename = output_path.file_name() + .and_then(|f| f.to_str()) + .unwrap_or("file"); + let callback = progress_callback.lock().await; + callback(1.0, filename, 0.0, Duration::from_secs(0)); + return Ok(()); + } + // If sizes don't match, start fresh + info!("Got 416 error but sizes don't match, starting fresh download"); + downloaded_size = 0; + // Make a new request without range header + let response = self.client.get(url) + .header(USER_AGENT, "Sinfar NWN Installer") + .send() + .await + .context("Failed to send HTTP request")?; + if !response.status().is_success() { + return Err(anyhow!("Server returned error status: {}", response.status())); + } + } else if !status.is_success() { + return Err(anyhow!("Server returned error status: {}", status)); + } + + info!("Total download size: {} bytes", content_length); + + // Open file in appropriate mode + let mut file = if downloaded_size > 0 { + info!("Resuming download from byte {}", downloaded_size); tokio::fs::OpenOptions::new() .write(true) .append(true) .open(output_path) .await? } else { - info!("Creating new file"); - downloaded_size = 0; + info!("Starting fresh download"); tokio::fs::File::create(output_path).await? }; @@ -179,18 +233,11 @@ impl DownloadManager { // Always download the content files let content_zip_path = self.temp_dir.join("sinfar_all_files_v30.7z"); - // Check if file already exists and is of expected size - if !self.file_exists_with_size(&content_zip_path, 100_000_000) { - // File doesn't exist or is too small, download it - self.download_file( - "https://sinfar.net/haks/sinfar_all_files_v30.7z", - &content_zip_path - ).await?; - } else { - // File exists, update progress to 100% - let callback = self.progress_callback.lock().await; - callback(1.0, "sinfar_all_files_v30.7z", 0.0, Duration::from_secs(0)); - } + // Always try to download/resume the content files - don't skip based on existing size + self.download_file( + "https://sinfar.net/haks/sinfar_all_files_v30.7z", + &content_zip_path + ).await?; downloaded_files.push(content_zip_path); @@ -198,15 +245,11 @@ impl DownloadManager { if *game_type == GameType::Diamond { let launcher_path = self.temp_dir.join("sinfarx.exe"); - if !self.file_exists_with_size(&launcher_path, 10_000) { - self.download_file( - "https://nwn.sinfar.net/files/sinfarx.exe", - &launcher_path - ).await?; - } else { - let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarx.exe", 0.0, Duration::from_secs(0)); - } + // Always attempt download/resume + self.download_file( + "https://nwn.sinfar.net/files/sinfarx.exe", + &launcher_path + ).await?; downloaded_files.push(launcher_path); } else if *game_type == GameType::EnhancedEdition { @@ -215,15 +258,11 @@ impl DownloadManager { { let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip"); - if !self.file_exists_with_size(&launcher_zip_path, 10_000) { - self.download_file( - "https://nwn.sinfar.net/files/sinfarx/8181/win32_8181.zip", - &launcher_zip_path - ).await?; - } else { - let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0)); - } + // Always attempt download/resume + self.download_file( + "https://nwn.sinfar.net/files/sinfarx/8181/win32_8181.zip", + &launcher_zip_path + ).await?; downloaded_files.push(launcher_zip_path); } @@ -232,15 +271,11 @@ impl DownloadManager { { let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip"); - if !self.file_exists_with_size(&launcher_zip_path, 10_000) { - self.download_file( - "https://nwn.sinfar.net/files/sinfarx/8181/linux_8181.zip", - &launcher_zip_path - ).await?; - } else { - let callback = self.progress_callback.lock().await; - callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0)); - } + // Always attempt download/resume + self.download_file( + "https://nwn.sinfar.net/files/sinfarx/8181/linux_8181.zip", + &launcher_zip_path + ).await?; downloaded_files.push(launcher_zip_path); } diff --git a/rust/src/ui/download_progress.rs b/rust/src/ui/download_progress.rs index 284a994..192eefc 100644 --- a/rust/src/ui/download_progress.rs +++ b/rust/src/ui/download_progress.rs @@ -1,5 +1,5 @@ pub mod download_progress { - use eframe::egui::{Ui, ProgressBar}; + use eframe::egui::{Ui, ProgressBar, Color32}; use crate::app::SinfarInstallerApp; use std::time::Duration; @@ -35,18 +35,22 @@ pub mod download_progress { 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 Ok(state) = app.download_state.lock() { + if state.completed { + ui.colored_label(Color32::GREEN, "Download Complete"); + } else 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)); + }); + } } }); ui.add_space(10.0); - // Add cancel button during active download + // Add cancel button only during active download if app.download_progress > 0.0 && app.download_progress < 1.0 { if ui.button("Cancel Download").clicked() { app.cancel_download(); @@ -54,7 +58,7 @@ pub mod download_progress { } if let Some(error) = &app.download_error { - ui.colored_label(eframe::egui::Color32::RED, format!("Error: {}", error)); + ui.colored_label(Color32::RED, format!("Error: {}", error)); if ui.button("Retry").clicked() { app.download_error = None; app.download_progress = 0.0;