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
|
// 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() {
|
if let Ok(mut app_state) = progress_state.lock() {
|
||||||
app_state.progress = progress;
|
app_state.progress = progress;
|
||||||
|
app_state.estimated_remaining = remaining;
|
||||||
eframe_ctx.request_repaint();
|
eframe_ctx.request_repaint();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -10,19 +10,49 @@ use std::io::{Read, Seek};
|
|||||||
use crate::app::GameType;
|
use crate::app::GameType;
|
||||||
|
|
||||||
pub struct ExtractionManager {
|
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 {
|
impl ExtractionManager {
|
||||||
pub fn new<F>(progress_callback: F) -> Self
|
pub fn new<F>(progress_callback: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(f32, &str) + Send + Sync + 'static
|
F: Fn(f32, &str, std::time::Duration) + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
progress_callback: Arc::new(Mutex::new(Box::new(progress_callback))),
|
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
|
/// Extract a 7z archive with progress reporting
|
||||||
pub async fn extract_7z(&self, archive_path: &Path, output_dir: &Path) -> Result<()> {
|
pub async fn extract_7z(&self, archive_path: &Path, output_dir: &Path) -> Result<()> {
|
||||||
info!("Starting extraction of 7z archive");
|
info!("Starting extraction of 7z archive");
|
||||||
@ -61,6 +91,8 @@ impl ExtractionManager {
|
|||||||
let archive_path = archive_path.to_path_buf();
|
let archive_path = archive_path.to_path_buf();
|
||||||
let output_dir = output_dir.to_path_buf();
|
let output_dir = output_dir.to_path_buf();
|
||||||
let progress_callback = self.progress_callback.clone();
|
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");
|
info!("Starting blocking extraction task");
|
||||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||||
@ -73,7 +105,9 @@ impl ExtractionManager {
|
|||||||
bytes_read: u64,
|
bytes_read: u64,
|
||||||
total_size: u64,
|
total_size: u64,
|
||||||
last_progress: f32,
|
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> {
|
impl<R: Read + Seek> Read for ProgressReader<R> {
|
||||||
@ -88,9 +122,29 @@ impl ExtractionManager {
|
|||||||
if progress - self.last_progress >= 0.01 {
|
if progress - self.last_progress >= 0.01 {
|
||||||
self.last_progress = progress;
|
self.last_progress = progress;
|
||||||
let progress_callback_clone = self.progress_callback.clone();
|
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 {
|
tokio::runtime::Handle::current().block_on(async move {
|
||||||
let callback = progress_callback_clone.lock().await;
|
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,
|
total_size: archive_size,
|
||||||
last_progress: 0.0,
|
last_progress: 0.0,
|
||||||
progress_callback: progress_callback.clone(),
|
progress_callback: progress_callback.clone(),
|
||||||
|
last_update: last_update.clone(),
|
||||||
|
last_processed: last_processed.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Starting decompression to: {}", output_dir.display());
|
info!("Starting decompression to: {}", output_dir.display());
|
||||||
@ -127,7 +183,7 @@ impl ExtractionManager {
|
|||||||
let progress_callback_clone = progress_callback.clone();
|
let progress_callback_clone = progress_callback.clone();
|
||||||
tokio::runtime::Handle::current().block_on(async move {
|
tokio::runtime::Handle::current().block_on(async move {
|
||||||
let callback = progress_callback_clone.lock().await;
|
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");
|
info!("Extraction task completed successfully");
|
||||||
@ -151,6 +207,8 @@ impl ExtractionManager {
|
|||||||
let archive_path = archive_path.to_path_buf();
|
let archive_path = archive_path.to_path_buf();
|
||||||
let output_dir = output_dir.to_path_buf();
|
let output_dir = output_dir.to_path_buf();
|
||||||
let progress_callback = self.progress_callback.clone();
|
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<()> {
|
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||||
let file = fs::File::open(&archive_path)?;
|
let file = fs::File::open(&archive_path)?;
|
||||||
@ -217,9 +275,29 @@ impl ExtractionManager {
|
|||||||
// Update progress less frequently to reduce overhead
|
// Update progress less frequently to reduce overhead
|
||||||
if i % 10 == 0 || progress >= 1.0 {
|
if i % 10 == 0 || progress >= 1.0 {
|
||||||
let progress_callback_clone = progress_callback.clone();
|
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 {
|
tokio::runtime::Handle::current().block_on(async move {
|
||||||
let callback = progress_callback_clone.lock().await;
|
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();
|
let progress_callback_clone = progress_callback.clone();
|
||||||
tokio::runtime::Handle::current().block_on(async move {
|
tokio::runtime::Handle::current().block_on(async move {
|
||||||
let callback = progress_callback_clone.lock().await;
|
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");
|
info!("ZIP extraction completed successfully");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pub mod download_progress {
|
pub mod download_progress {
|
||||||
use eframe::egui::{Ui, ProgressBar, Color32};
|
use eframe::egui::{Ui, ProgressBar, Color32};
|
||||||
use crate::app::SinfarInstallerApp;
|
use crate::app::SinfarInstallerApp;
|
||||||
use std::time::Duration;
|
use crate::ui::format_duration;
|
||||||
|
|
||||||
fn format_bytes(bytes: f64) -> String {
|
fn format_bytes(bytes: f64) -> String {
|
||||||
const KB: f64 = 1024.0;
|
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) {
|
pub fn render(ui: &mut Ui, app: &mut SinfarInstallerApp) {
|
||||||
ui.heading("Downloading Sinfar Custom Content");
|
ui.heading("Downloading Sinfar Custom Content");
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
@ -1,18 +1,31 @@
|
|||||||
pub mod installation_progress {
|
pub mod installation_progress {
|
||||||
use eframe::egui::{Ui, ProgressBar};
|
use eframe::egui::{Ui, ProgressBar, Color32};
|
||||||
use crate::app::SinfarInstallerApp;
|
use crate::app::SinfarInstallerApp;
|
||||||
|
use crate::ui::format_duration;
|
||||||
|
|
||||||
pub fn render(ui: &mut Ui, app: &mut SinfarInstallerApp) {
|
pub fn render(ui: &mut Ui, app: &mut SinfarInstallerApp) {
|
||||||
ui.heading("Installing Sinfar Custom Content");
|
ui.heading("Installing Sinfar Custom Content");
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Extracting files:");
|
ui.label("Extracting files:");
|
||||||
ui.add(ProgressBar::new(app.extraction_progress).show_percentage());
|
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 {
|
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() {
|
if ui.button("Retry").clicked() {
|
||||||
app.install_error = None;
|
app.install_error = None;
|
||||||
app.start_installation();
|
app.start_installation();
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
pub mod game_selection;
|
|
||||||
pub mod path_selection;
|
|
||||||
pub mod download_progress;
|
pub mod download_progress;
|
||||||
|
pub mod game_selection;
|
||||||
pub mod installation_progress;
|
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