Claude: Add estimated remaining time for extraction

This commit is contained in:
Fiery Imp 2025-04-24 14:57:50 -04:00
parent 70c0492d7a
commit 3d24ac8b57
5 changed files with 120 additions and 26 deletions

View File

@ -297,9 +297,10 @@ impl SinfarInstallerApp<> {
}
// Create extraction manager with progress callback
let extractor = crate::installer::extractor::ExtractionManager::new(move |progress: f32, _filename: &str| {
let extractor = crate::installer::extractor::ExtractionManager::new(move |progress: f32, _filename: &str, remaining: std::time::Duration| {
if let Ok(mut app_state) = progress_state.lock() {
app_state.progress = progress;
app_state.estimated_remaining = remaining;
eframe_ctx.request_repaint();
}
});

View File

@ -10,19 +10,49 @@ use std::io::{Read, Seek};
use crate::app::GameType;
pub struct ExtractionManager {
progress_callback: Arc<Mutex<Box<dyn Fn(f32, &str) + Send + Sync>>>,
progress_callback: Arc<Mutex<Box<dyn Fn(f32, &str, std::time::Duration) + Send + Sync>>>,
last_update: Arc<Mutex<std::time::Instant>>,
last_processed: Arc<Mutex<u64>>,
}
impl ExtractionManager {
pub fn new<F>(progress_callback: F) -> Self
where
F: Fn(f32, &str) + Send + Sync + 'static
F: Fn(f32, &str, std::time::Duration) + Send + Sync + 'static
{
Self {
progress_callback: Arc::new(Mutex::new(Box::new(progress_callback))),
last_update: Arc::new(Mutex::new(std::time::Instant::now())),
last_processed: Arc::new(Mutex::new(0)),
}
}
// Helper function to calculate estimated time remaining
async fn update_progress(&self, processed: u64, total: u64, filename: &str) {
let progress = processed as f32 / total as f32;
let now = std::time::Instant::now();
let mut last_update = self.last_update.lock().await;
let mut last_processed = self.last_processed.lock().await;
let elapsed = now.duration_since(*last_update).as_secs_f64();
let bytes_since_last = processed - *last_processed;
let speed = bytes_since_last as f64 / elapsed;
let remaining_bytes = total - processed;
let estimated_remaining = if speed > 0.0 {
std::time::Duration::from_secs_f64(remaining_bytes as f64 / speed)
} else {
std::time::Duration::from_secs(0)
};
*last_update = now;
*last_processed = processed;
let callback = self.progress_callback.lock().await;
callback(progress, filename, estimated_remaining);
}
/// Extract a 7z archive with progress reporting
pub async fn extract_7z(&self, archive_path: &Path, output_dir: &Path) -> Result<()> {
info!("Starting extraction of 7z archive");
@ -61,6 +91,8 @@ impl ExtractionManager {
let archive_path = archive_path.to_path_buf();
let output_dir = output_dir.to_path_buf();
let progress_callback = self.progress_callback.clone();
let last_update = self.last_update.clone();
let last_processed = self.last_processed.clone();
info!("Starting blocking extraction task");
tokio::task::spawn_blocking(move || -> Result<()> {
@ -73,7 +105,9 @@ impl ExtractionManager {
bytes_read: u64,
total_size: u64,
last_progress: f32,
progress_callback: Arc<Mutex<Box<dyn Fn(f32, &str) + Send + Sync>>>,
progress_callback: Arc<Mutex<Box<dyn Fn(f32, &str, std::time::Duration) + Send + Sync>>>,
last_update: Arc<Mutex<std::time::Instant>>,
last_processed: Arc<Mutex<u64>>,
}
impl<R: Read + Seek> Read for ProgressReader<R> {
@ -88,9 +122,29 @@ impl ExtractionManager {
if progress - self.last_progress >= 0.01 {
self.last_progress = progress;
let progress_callback_clone = self.progress_callback.clone();
let last_update_clone = self.last_update.clone();
let last_processed_clone = self.last_processed.clone();
tokio::runtime::Handle::current().block_on(async move {
let callback = progress_callback_clone.lock().await;
callback(progress, "Extracting...");
let now = std::time::Instant::now();
let mut last_update = last_update_clone.lock().await;
let mut last_processed = last_processed_clone.lock().await;
let elapsed = now.duration_since(*last_update).as_secs_f64();
let bytes_since_last = self.bytes_read - *last_processed;
let speed = bytes_since_last as f64 / elapsed;
let remaining_bytes = self.total_size - self.bytes_read;
let estimated_remaining = if speed > 0.0 {
std::time::Duration::from_secs_f64(remaining_bytes as f64 / speed)
} else {
std::time::Duration::from_secs(0)
};
*last_update = now;
*last_processed = self.bytes_read;
callback(progress, "Extracting...", estimated_remaining);
});
}
@ -110,6 +164,8 @@ impl ExtractionManager {
total_size: archive_size,
last_progress: 0.0,
progress_callback: progress_callback.clone(),
last_update: last_update.clone(),
last_processed: last_processed.clone(),
};
info!("Starting decompression to: {}", output_dir.display());
@ -127,7 +183,7 @@ impl ExtractionManager {
let progress_callback_clone = progress_callback.clone();
tokio::runtime::Handle::current().block_on(async move {
let callback = progress_callback_clone.lock().await;
callback(1.0, "Complete");
callback(1.0, "Complete", std::time::Duration::from_secs(0));
});
info!("Extraction task completed successfully");
@ -151,6 +207,8 @@ impl ExtractionManager {
let archive_path = archive_path.to_path_buf();
let output_dir = output_dir.to_path_buf();
let progress_callback = self.progress_callback.clone();
let last_update = self.last_update.clone();
let last_processed = self.last_processed.clone();
tokio::task::spawn_blocking(move || -> Result<()> {
let file = fs::File::open(&archive_path)?;
@ -217,9 +275,29 @@ impl ExtractionManager {
// Update progress less frequently to reduce overhead
if i % 10 == 0 || progress >= 1.0 {
let progress_callback_clone = progress_callback.clone();
let last_update_clone = last_update.clone();
let last_processed_clone = last_processed.clone();
tokio::runtime::Handle::current().block_on(async move {
let callback = progress_callback_clone.lock().await;
callback(progress, filename);
let now = std::time::Instant::now();
let mut last_update = last_update_clone.lock().await;
let mut last_processed = last_processed_clone.lock().await;
let elapsed = now.duration_since(*last_update).as_secs_f64();
let bytes_since_last = processed_bytes - *last_processed;
let speed = bytes_since_last as f64 / elapsed;
let remaining_bytes = total_size - processed_bytes;
let estimated_remaining = if speed > 0.0 {
std::time::Duration::from_secs_f64(remaining_bytes as f64 / speed)
} else {
std::time::Duration::from_secs(0)
};
*last_update = now;
*last_processed = processed_bytes;
callback(progress, filename, estimated_remaining);
});
}
}
@ -238,7 +316,7 @@ impl ExtractionManager {
let progress_callback_clone = progress_callback.clone();
tokio::runtime::Handle::current().block_on(async move {
let callback = progress_callback_clone.lock().await;
callback(1.0, "Complete");
callback(1.0, "Complete", std::time::Duration::from_secs(0));
});
info!("ZIP extraction completed successfully");

View File

@ -1,7 +1,7 @@
pub mod download_progress {
use eframe::egui::{Ui, ProgressBar, Color32};
use crate::app::SinfarInstallerApp;
use std::time::Duration;
use crate::ui::format_duration;
fn format_bytes(bytes: f64) -> String {
const KB: f64 = 1024.0;
@ -16,15 +16,6 @@ pub mod download_progress {
}
}
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);

View File

@ -1,18 +1,31 @@
pub mod installation_progress {
use eframe::egui::{Ui, ProgressBar};
use eframe::egui::{Ui, ProgressBar, Color32};
use crate::app::SinfarInstallerApp;
use crate::ui::format_duration;
pub fn render(ui: &mut Ui, app: &mut SinfarInstallerApp) {
ui.heading("Installing Sinfar Custom Content");
ui.add_space(10.0);
ui.horizontal(|ui| {
ui.label("Extracting files:");
ui.add(ProgressBar::new(app.extraction_progress).show_percentage());
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("Extracting files:");
ui.add(ProgressBar::new(app.extraction_progress).show_percentage());
});
if let Ok(state) = app.download_state.lock() {
if state.completed {
ui.colored_label(Color32::GREEN, "Installation Complete");
} else if app.extraction_progress > 0.0 && app.extraction_progress < 1.0 {
ui.horizontal(|ui| {
ui.label(format_duration(state.estimated_remaining));
});
}
}
});
if let Some(error) = &app.install_error {
ui.colored_label(eframe::egui::Color32::RED, error);
ui.colored_label(Color32::RED, error);
if ui.button("Retry").clicked() {
app.install_error = None;
app.start_installation();

View File

@ -1,4 +1,15 @@
pub mod game_selection;
pub mod path_selection;
pub mod download_progress;
pub mod game_selection;
pub mod installation_progress;
pub mod path_selection;
use std::time::Duration;
pub fn format_duration(duration: Duration) -> String {
let secs = duration.as_secs();
if secs >= 3600 {
format!("{:02}h{:02}m{:02}s estimated remaining time", secs / 3600, (secs % 3600) / 60, secs % 60)
} else {
format!("{:02}m{:02}s estimated remaining time", secs / 60, secs % 60)
}
}