From 98def5fa73cd99c4b34266664db5e05ddf5cff98 Mon Sep 17 00:00:00 2001 From: Fiery Imp Date: Wed, 23 Apr 2025 22:18:40 -0400 Subject: [PATCH] Claude: The file are now extracting, albeit slowly --- rust/src/installer/extractor.rs | 168 +++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 48 deletions(-) diff --git a/rust/src/installer/extractor.rs b/rust/src/installer/extractor.rs index 38ef29f..7e87cd0 100644 --- a/rust/src/installer/extractor.rs +++ b/rust/src/installer/extractor.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use log::{info, error}; use std::fs; -use std::io::Read; +use std::io::{Read, Seek}; use crate::app::GameType; pub struct ExtractionManager { @@ -35,7 +35,7 @@ impl ExtractionManager { return Err(anyhow::anyhow!("Archive file does not exist")); } - // Validate 7z signature + // Validate 7z signature and get archive size let mut file = fs::File::open(&archive_path)?; let mut signature = [0u8; 6]; file.read_exact(&mut signature)?; @@ -54,8 +54,8 @@ impl ExtractionManager { } // Get file size for progress calculation - let file_size = fs::metadata(archive_path)?.len(); - info!("Archive size: {} bytes", file_size); + let archive_size = fs::metadata(archive_path)?.len(); + info!("Archive size: {} bytes", archive_size); // We'll handle extraction in a blocking task since sevenz-rust is synchronous let archive_path = archive_path.to_path_buf(); @@ -64,40 +64,72 @@ impl ExtractionManager { info!("Starting blocking extraction task"); tokio::task::spawn_blocking(move || -> Result<()> { - info!("Opening archive file for reading"); + // Open the archive for processing let archive_file = fs::File::open(&archive_path)?; + // Create a wrapper around the file that tracks read progress + struct ProgressReader { + inner: R, + bytes_read: u64, + total_size: u64, + last_progress: f32, + progress_callback: Arc>>, + } + + impl Read for ProgressReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let bytes = self.inner.read(buf)?; + self.bytes_read += bytes as u64; + + // Calculate progress as a percentage + let progress = self.bytes_read as f32 / self.total_size as f32; + + // Only update progress if it has changed by at least 1% + if progress - self.last_progress >= 0.01 { + self.last_progress = progress; + let progress_callback_clone = self.progress_callback.clone(); + tokio::runtime::Handle::current().block_on(async move { + let callback = progress_callback_clone.lock().await; + callback(progress, "Extracting..."); + }); + } + + Ok(bytes) + } + } + + impl Seek for ProgressReader { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.inner.seek(pos) + } + } + + let progress_reader = ProgressReader { + inner: archive_file, + bytes_read: 0, + total_size: archive_size, + last_progress: 0.0, + progress_callback: progress_callback.clone(), + }; + info!("Starting decompression to: {}", output_dir.display()); - // decompress takes a Read + Seek and a destination directory - match decompress(&archive_file, &output_dir) { + + // Decompress using our progress-tracking reader + match decompress(progress_reader, &output_dir) { Ok(_) => info!("Decompression completed successfully"), Err(e) => { error!("Decompression failed: {}", e); return Err(anyhow::anyhow!("Decompression failed: {}", e)); } } - - // List contents of output directory to verify extraction - if let Ok(entries) = fs::read_dir(&output_dir) { - info!("Listing extracted contents:"); - for entry in entries { - if let Ok(entry) = entry { - info!(" {}", entry.path().display()); - } - } - } - + // Call progress update with 100% after decompression - let filename = archive_path.file_name() - .and_then(|f| f.to_str()) - .unwrap_or("archive"); - 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, filename); // 100% done + callback(1.0, "Complete"); }); - + info!("Extraction task completed successfully"); Ok(()) }).await??; @@ -124,32 +156,72 @@ impl ExtractionManager { let file = fs::File::open(&archive_path)?; let mut archive = zip::ZipArchive::new(file)?; - let total_entries = archive.len(); - info!("ZIP archive contains {} entries", total_entries); + // Calculate total uncompressed size + let total_size: u64 = { + let mut size = 0; + for i in 0..archive.len() { + if let Ok(file) = archive.by_index(i) { + size += file.size(); + } + } + size + }; - for i in 0..total_entries { + info!("ZIP archive total uncompressed size: {} bytes", total_size); + + let mut processed_bytes: u64 = 0; + + // Process files in batches for better performance + let mut pending_dirs = Vec::new(); + + // First pass: create all directories (this prevents race conditions) + for i in 0..archive.len() { + let file = archive.by_index(i)?; + let outpath = match file.enclosed_name() { + Some(path) => output_dir.join(path), + None => continue, + }; + + if file.name().ends_with('/') { + pending_dirs.push(outpath); + } else if let Some(p) = outpath.parent() { + pending_dirs.push(p.to_path_buf()); + } + } + + // Create all directories at once + for dir in pending_dirs.iter().collect::>() { + fs::create_dir_all(dir)?; + } + + // Second pass: extract all files + for i in 0..archive.len() { let mut file = archive.by_index(i)?; let outpath = match file.enclosed_name() { Some(path) => output_dir.join(path), None => continue, }; - // Update progress - let progress = (i + 1) as f32 / total_entries as f32; - let filename = outpath.file_name() - .and_then(|f| f.to_str()) - .unwrap_or("unknown"); - - if (*file.name()).ends_with('/') { - fs::create_dir_all(&outpath)?; - } else { - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(p)?; - } - } + if !file.name().ends_with('/') { let mut outfile = fs::File::create(&outpath)?; + let file_size = file.size(); std::io::copy(&mut file, &mut outfile)?; + processed_bytes += file_size; + + // Update progress based on processed bytes + let progress = processed_bytes as f32 / total_size as f32; + let filename = outpath.file_name() + .and_then(|f| f.to_str()) + .unwrap_or("unknown"); + + // Update progress less frequently to reduce overhead + if i % 10 == 0 || progress >= 1.0 { + let progress_callback_clone = progress_callback.clone(); + tokio::runtime::Handle::current().block_on(async move { + let callback = progress_callback_clone.lock().await; + callback(progress, filename); + }); + } } // Fix file permissions on Unix @@ -160,15 +232,15 @@ impl ExtractionManager { fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?; } } - - // Update progress on the tokio runtime - let progress_callback_clone = progress_callback.clone(); - tokio::runtime::Handle::current().block_on(async move { - let callback = progress_callback_clone.lock().await; - callback(progress, filename); - }); } + // Ensure we show 100% at the end + 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"); + }); + info!("ZIP extraction completed successfully"); Ok(()) }).await??;