diff --git a/rust/src/app.rs b/rust/src/app.rs index 362b29e..20d1418 100644 --- a/rust/src/app.rs +++ b/rust/src/app.rs @@ -271,14 +271,50 @@ impl SinfarInstallerApp<> { } pub fn start_installation(&mut self) { - - - // This would be spawned in a separate thread - //let game_type = self.game_type.clone().unwrap(); - //let install_path = self.install_path.clone().unwrap(); - //let ee_exe_path = self.ee_exe_path.clone(); - - // Implementation with sevenz-rust would go here - // Update self.extraction_progress as files are extracted + // Use tokio runtime to handle the async installation + let install_path = self.install_path.clone().expect("Install path should be set"); + let ee_exe_path = self.ee_exe_path.clone(); + let download_state = self.download_state.clone(); + let progress_state = self.download_state.clone(); + let eframe_ctx = self.eframe_ctx.clone().expect("eframe context should be set"); + let runtime = self.runtime.clone(); + let game_type = self.game_type.clone().expect("Game type should be set"); + + // Create extraction manager with progress callback + let extractor = crate::installer::extractor::ExtractionManager::new(move |progress: f32, _filename: &str| { + if let Ok(mut app_state) = progress_state.lock() { + app_state.progress = progress; + } + eframe_ctx.request_repaint(); + }); + + // Get downloaded files before spawning the async task + let downloaded_files = if let Ok(state) = download_state.lock() { + state.files.clone() + } else { + None + }; + + if let Some(files) = downloaded_files { + runtime.spawn(async move { + // Try to install the files + if let Err(e) = extractor.install_all_files( + &files, + &install_path, + &game_type, + ee_exe_path.as_deref() + ).await { + if let Ok(mut app_state) = download_state.lock() { + app_state.error = Some(format!("Installation failed: {}", e)); + } + } else { + // Mark as complete on success + if let Ok(mut app_state) = download_state.lock() { + app_state.completed = true; + app_state.progress = 1.0; + } + } + }); + } } } \ No newline at end of file diff --git a/rust/src/installer/extractor.rs b/rust/src/installer/extractor.rs index 5275ccd..38ef29f 100644 --- a/rust/src/installer/extractor.rs +++ b/rust/src/installer/extractor.rs @@ -1,13 +1,13 @@ // src/installer/extractor.rs use anyhow::{Result, Context}; -use sevenz_rust::{Archive, BlockDecoder, decompress}; +use sevenz_rust::decompress; use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::Mutex; -use log::{info, warn, error}; +use log::{info, error}; use std::fs; -use std::io::{Read, Write}; -use std::collections::HashMap; +use std::io::Read; +use crate::app::GameType; pub struct ExtractionManager { progress_callback: Arc>>, @@ -25,27 +25,67 @@ impl ExtractionManager { /// Extract a 7z archive with progress reporting pub async fn extract_7z(&self, archive_path: &Path, output_dir: &Path) -> Result<()> { - info!("Extracting 7z archive: {} -> {}", archive_path.display(), output_dir.display()); + info!("Starting extraction of 7z archive"); + info!("Archive path: {}", archive_path.display()); + info!("Output directory: {}", output_dir.display()); + + // Check if archive exists + if !archive_path.exists() { + error!("Archive file does not exist: {}", archive_path.display()); + return Err(anyhow::anyhow!("Archive file does not exist")); + } + + // Validate 7z signature + let mut file = fs::File::open(&archive_path)?; + let mut signature = [0u8; 6]; + file.read_exact(&mut signature)?; + + // 7z files start with bytes [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C] + if signature != [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C] { + error!("Invalid 7z file signature: {:?}", signature); + return Err(anyhow::anyhow!("Invalid 7z file - got wrong signature. The downloaded file might be corrupted or might be an error page")); + } + info!("7z signature validation passed"); // Make sure the output directory exists if !output_dir.exists() { + info!("Creating output directory: {}", output_dir.display()); tokio::fs::create_dir_all(output_dir).await?; } // Get file size for progress calculation let file_size = fs::metadata(archive_path)?.len(); + info!("Archive size: {} bytes", file_size); // We'll handle extraction in a blocking task since sevenz-rust is synchronous let archive_path = archive_path.to_path_buf(); let output_dir = output_dir.to_path_buf(); let progress_callback = self.progress_callback.clone(); + info!("Starting blocking extraction task"); tokio::task::spawn_blocking(move || -> Result<()> { + info!("Opening archive file for reading"); let archive_file = fs::File::open(&archive_path)?; - let out = fs::create_dir_all(&output_dir)?; + info!("Starting decompression to: {}", output_dir.display()); // decompress takes a Read + Seek and a destination directory - decompress(&archive_file, &output_dir)?; + match decompress(&archive_file, &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() @@ -58,9 +98,11 @@ impl ExtractionManager { callback(1.0, filename); // 100% done }); + info!("Extraction task completed successfully"); Ok(()) }).await??; + info!("7z extraction process completed"); Ok(()) } @@ -134,53 +176,78 @@ impl ExtractionManager { Ok(()) } - /// Handles extraction of all downloaded files to their appropriate locations + /// Handles extraction of all downloaded files to their appropriate locations pub async fn install_all_files( &self, downloaded_files: &[PathBuf], install_path: &Path, - game_type: &str, + game_type: &GameType, ee_exe_path: Option<&Path> ) -> Result<()> { + info!("Starting installation of all files"); + info!("Install path: {}", install_path.display()); + info!("Game type: {:?}", game_type); + if let Some(ee_path) = ee_exe_path { + info!("EE executable path: {}", ee_path.display()); + } + + info!("Available downloaded files:"); + for file in downloaded_files { + info!(" {}", file.display()); + } + // Find and extract the main content files let content_archive = downloaded_files.iter() .find(|p| p.file_name().map_or(false, |f| f.to_str().unwrap_or("").contains("sinfar_all_files"))) .context("Content archive not found in downloaded files")?; + + info!("Found content archive: {}", content_archive.display()); // Extract main content + info!("Starting extraction of main content"); self.extract_7z(content_archive, install_path).await?; // Handle game-specific files - if game_type.to_lowercase() == "diamond" { - // For Diamond, find and copy the launcher - let launcher = downloaded_files.iter() - .find(|p| p.file_name().map_or(false, |f| f.to_str().unwrap_or("").contains("sinfarx.exe"))) - .context("Launcher executable not found in downloaded files")?; - - // Copy launcher to install directory - let target_path = install_path.join("sinfarx.exe"); - tokio::fs::copy(launcher, &target_path).await?; - - } else if game_type.to_lowercase() == "enhancededition" || game_type.to_lowercase() == "ee" { - // For EE, find the launcher zip - let launcher_zip = downloaded_files.iter() - .find(|p| p.file_name().map_or(false, |f| f.to_str().unwrap_or("").contains("sinfarLauncher.zip"))) - .context("Launcher ZIP not found in downloaded files")?; - - // Extract to the EE executable directory - if let Some(ee_path) = ee_exe_path { - // Create the bin/win32_8181 directory if it doesn't exist (Windows) - // or bin/linux_8181 directory (Linux) - #[cfg(windows)] - let extract_dir = ee_path.join("bin").join("win32_8181"); - - #[cfg(unix)] - let extract_dir = ee_path.join("bin").join("linux_8181"); - - // Extract the launcher - self.extract_zip(launcher_zip, &extract_dir).await?; - } else { - return Err(anyhow::anyhow!("EE executable path is required for EE installation")); + info!("Processing game-specific files"); + match game_type { + GameType::Diamond => { + info!("Installing Diamond edition launcher"); + // For Diamond, find and copy the launcher + let launcher = downloaded_files.iter() + .find(|p| p.file_name().map_or(false, |f| f.to_str().unwrap_or("").contains("sinfarx.exe"))) + .context("Launcher executable not found in downloaded files")?; + + // Copy launcher to install directory + let target_path = install_path.join("sinfarx.exe"); + info!("Copying launcher from {} to {}", launcher.display(), target_path.display()); + tokio::fs::copy(launcher, &target_path).await?; + info!("Launcher copied successfully"); + } + GameType::EnhancedEdition => { + info!("Installing Enhanced Edition launcher"); + // For EE, find the launcher zip + let launcher_zip = downloaded_files.iter() + .find(|p| p.file_name().map_or(false, |f| f.to_str().unwrap_or("").contains("sinfarLauncher.zip"))) + .context("Launcher ZIP not found in downloaded files")?; + + // Extract to the EE executable directory + if let Some(ee_path) = ee_exe_path { + // Create the bin/win32_8181 directory if it doesn't exist (Windows) + // or bin/linux_8181 directory (Linux) + #[cfg(windows)] + let extract_dir = ee_path.join("bin").join("win32_8181"); + + #[cfg(unix)] + let extract_dir = ee_path.join("bin").join("linux_8181"); + + info!("Extracting EE launcher to {}", extract_dir.display()); + // Extract the launcher + self.extract_zip(launcher_zip, &extract_dir).await?; + info!("EE launcher extracted successfully"); + } else { + error!("EE executable path is required but not provided"); + return Err(anyhow::anyhow!("EE executable path is required for EE installation")); + } } }