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 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;

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();
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::<u64>().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);
}

View File

@ -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;