Claude: Add estimated remaining time for extraction
This commit is contained in:
parent
70c0492d7a
commit
3d24ac8b57
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user