Claude: Fine tuned download progress screen

This commit is contained in:
Fiery Imp 2025-04-23 20:37:19 -04:00
parent ae06bec3ed
commit 9bc602c136
3 changed files with 131 additions and 88 deletions

View File

@ -97,8 +97,12 @@ impl<> eframe::App for SinfarInstallerApp<> {
if self.state == InstallerState::Download { if self.state == InstallerState::Download {
if let Ok(state) = self.download_state.lock() { if let Ok(state) = self.download_state.lock() {
// Only update progress if not cancelled // Set progress to 100% if completed
if !state.cancelled { 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_progress = state.progress;
self.download_speed = state.speed; self.download_speed = state.speed;
self.estimated_remaining = state.estimated_remaining; self.estimated_remaining = state.estimated_remaining;

View File

@ -62,65 +62,119 @@ impl DownloadManager {
} }
} }
// Check if we can resume a previous download
// Prepare headers for potential resume
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static("Sinfar NWN Installer")); 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; let status = response_head.status();
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")?;
// Check if the server supports resuming // Get content length for progress calculation
let can_resume = response.status().is_success() && response.status().as_u16() == 206; let total_size = response_head.content_length().unwrap_or(0);
let status = response.status(); let content_length = if status.as_u16() == 206 {
if let Some(content_range) = response_head.headers().get("content-range") {
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") {
let range_str = content_range.to_str().unwrap_or(""); let range_str = content_range.to_str().unwrap_or("");
if let Some(size_part) = range_str.split('/').nth(1) { if let Some(size_part) = range_str.split('/').nth(1) {
size_part.parse::<u64>().unwrap_or(0) size_part.parse::<u64>().unwrap_or(0)
} else { } else {
response.content_length().unwrap_or(0) + downloaded_size total_size
} }
} else { } else {
response.content_length().unwrap_or(0) + downloaded_size total_size
} }
} else { } 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) // Prepare headers for potential resume
let mut file = if can_resume && downloaded_size > 0 { let mut headers_toresume = HeaderMap::new();
info!("Opening file for append"); 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() tokio::fs::OpenOptions::new()
.write(true) .write(true)
.append(true) .append(true)
.open(output_path) .open(output_path)
.await? .await?
} else { } else {
info!("Creating new file"); info!("Starting fresh download");
downloaded_size = 0;
tokio::fs::File::create(output_path).await? tokio::fs::File::create(output_path).await?
}; };
@ -179,18 +233,11 @@ impl DownloadManager {
// Always download the content files // Always download the content files
let content_zip_path = self.temp_dir.join("sinfar_all_files_v30.7z"); let content_zip_path = self.temp_dir.join("sinfar_all_files_v30.7z");
// Check if file already exists and is of expected size // Always try to download/resume the content files - don't skip based on existing size
if !self.file_exists_with_size(&content_zip_path, 100_000_000) { self.download_file(
// File doesn't exist or is too small, download it "https://sinfar.net/haks/sinfar_all_files_v30.7z",
self.download_file( &content_zip_path
"https://sinfar.net/haks/sinfar_all_files_v30.7z", ).await?;
&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));
}
downloaded_files.push(content_zip_path); downloaded_files.push(content_zip_path);
@ -198,15 +245,11 @@ impl DownloadManager {
if *game_type == GameType::Diamond { if *game_type == GameType::Diamond {
let launcher_path = self.temp_dir.join("sinfarx.exe"); let launcher_path = self.temp_dir.join("sinfarx.exe");
if !self.file_exists_with_size(&launcher_path, 10_000) { // Always attempt download/resume
self.download_file( self.download_file(
"https://nwn.sinfar.net/files/sinfarx.exe", "https://nwn.sinfar.net/files/sinfarx.exe",
&launcher_path &launcher_path
).await?; ).await?;
} else {
let callback = self.progress_callback.lock().await;
callback(1.0, "sinfarx.exe", 0.0, Duration::from_secs(0));
}
downloaded_files.push(launcher_path); downloaded_files.push(launcher_path);
} else if *game_type == GameType::EnhancedEdition { } else if *game_type == GameType::EnhancedEdition {
@ -215,15 +258,11 @@ impl DownloadManager {
{ {
let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip"); let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip");
if !self.file_exists_with_size(&launcher_zip_path, 10_000) { // Always attempt download/resume
self.download_file( self.download_file(
"https://nwn.sinfar.net/files/sinfarx/8181/win32_8181.zip", "https://nwn.sinfar.net/files/sinfarx/8181/win32_8181.zip",
&launcher_zip_path &launcher_zip_path
).await?; ).await?;
} else {
let callback = self.progress_callback.lock().await;
callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0));
}
downloaded_files.push(launcher_zip_path); downloaded_files.push(launcher_zip_path);
} }
@ -232,15 +271,11 @@ impl DownloadManager {
{ {
let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip"); let launcher_zip_path = self.temp_dir.join("sinfarLauncher.zip");
if !self.file_exists_with_size(&launcher_zip_path, 10_000) { // Always attempt download/resume
self.download_file( self.download_file(
"https://nwn.sinfar.net/files/sinfarx/8181/linux_8181.zip", "https://nwn.sinfar.net/files/sinfarx/8181/linux_8181.zip",
&launcher_zip_path &launcher_zip_path
).await?; ).await?;
} else {
let callback = self.progress_callback.lock().await;
callback(1.0, "sinfarLauncher.zip", 0.0, Duration::from_secs(0));
}
downloaded_files.push(launcher_zip_path); downloaded_files.push(launcher_zip_path);
} }

View File

@ -1,5 +1,5 @@
pub mod download_progress { pub mod download_progress {
use eframe::egui::{Ui, ProgressBar}; use eframe::egui::{Ui, ProgressBar, Color32};
use crate::app::SinfarInstallerApp; use crate::app::SinfarInstallerApp;
use std::time::Duration; use std::time::Duration;
@ -35,18 +35,22 @@ pub mod download_progress {
ui.add(ProgressBar::new(app.download_progress).show_percentage()); ui.add(ProgressBar::new(app.download_progress).show_percentage());
}); });
if app.download_progress > 0.0 && app.download_progress < 1.0 { if let Ok(state) = app.download_state.lock() {
ui.horizontal(|ui| { if state.completed {
ui.label(format_bytes(app.download_speed)); ui.colored_label(Color32::GREEN, "Download Complete");
ui.label(""); } else if app.download_progress > 0.0 && app.download_progress < 1.0 {
ui.label(format_duration(app.estimated_remaining)); ui.horizontal(|ui| {
}); ui.label(format_bytes(app.download_speed));
ui.label("");
ui.label(format_duration(app.estimated_remaining));
});
}
} }
}); });
ui.add_space(10.0); 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 app.download_progress > 0.0 && app.download_progress < 1.0 {
if ui.button("Cancel Download").clicked() { if ui.button("Cancel Download").clicked() {
app.cancel_download(); app.cancel_download();
@ -54,7 +58,7 @@ pub mod download_progress {
} }
if let Some(error) = &app.download_error { 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() { if ui.button("Retry").clicked() {
app.download_error = None; app.download_error = None;
app.download_progress = 0.0; app.download_progress = 0.0;