diff --git a/rust/src/app.rs b/rust/src/app.rs index 0d0868c..1935673 100644 --- a/rust/src/app.rs +++ b/rust/src/app.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::installer::validator::*; use crate::installer::downloader::DownloadManager; +use crate::installer::SevenZDearchiver; use crate::ui::download_progress::download_progress; use crate::ui::game_selection::game_selection; use crate::ui::installation_progress::installation_progress; @@ -182,6 +183,7 @@ impl<> eframe::App for SinfarInstallerApp<> { if self.state == InstallerState::Download { self.start_download(); } else if self.state == InstallerState::Installing { + //self.start_extract(); self.start_installation(); } } @@ -335,4 +337,40 @@ impl SinfarInstallerApp<> { }); } } + + pub fn start_extract(&mut self) { + + let dearchiver = crate::installer::SevenZDearchiver::new(); + let download_state = self.download_state.clone(); + let error_state = download_state.clone(); + let eframe_ctx = self.eframe_ctx.clone().expect("eframe context should be set"); + + // Placeholder paths for testing + let archive_path = std::path::PathBuf::from("C:/Users/Samuel/AppData/Local/Temp/sinfar_installer/sinfar_all_files_v30.7z"); + let output_dir = std::path::PathBuf::from("C:/Users/Samuel/AppData/Local/Temp/sinfar_installer/test"); + + self.runtime.spawn(async move { + if let Err(e) = dearchiver.extract( + &archive_path, + &output_dir, + move |progress, eta| { + if let Ok(mut state) = download_state.lock() { + state.progress = progress; + state.estimated_remaining = eta; + if state.progress == 1.0 { + state.completed = true; + } + else { + state.completed = false; + } + eframe_ctx.request_repaint(); + } + } + ).await { + if let Ok(mut state) = error_state.lock() { + state.error = Some(format!("Extraction failed: {}", e)); + } + } + }); + } } \ No newline at end of file diff --git a/rust/src/installer/7z_dearchiver.rs b/rust/src/installer/7z_dearchiver.rs new file mode 100644 index 0000000..5af2319 --- /dev/null +++ b/rust/src/installer/7z_dearchiver.rs @@ -0,0 +1,112 @@ +use anyhow::Result; +use std::path::Path; +use std::sync::Arc; +use tokio::sync::{Mutex, watch}; +use std::io::{Read, Seek}; +use std::fs; +use std::time::{Duration, Instant}; + +pub struct SevenZDearchiver { + cancel_tx: watch::Sender, + cancel_rx: watch::Receiver, +} + +impl SevenZDearchiver { + pub fn new() -> Self { + let (cancel_tx, cancel_rx) = watch::channel(false); + Self { + cancel_tx, + cancel_rx, + } + } + + pub fn cancel(&self) { + let _ = self.cancel_tx.send(true); + } + + pub async fn extract(&self, archive_path: &Path, output_dir: &Path, progress_callback: F) -> Result<()> + where + F: Fn(f32, Duration) + Send + Sync + 'static + { + // Create wrapper for progress tracking + struct ProgressReader { + inner: R, + bytes_read: u64, + total_size: u64, + last_update: Instant, + cancel_signal: watch::Receiver, + progress_callback: Arc>, + } + + impl Read for ProgressReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // Check cancellation + if *self.cancel_signal.borrow() { + return Ok(0); // Return 0 to indicate EOF and stop extraction + } + + let bytes = self.inner.read(buf)?; + self.bytes_read += bytes as u64; + + let now = Instant::now(); + let elapsed = now.duration_since(self.last_update); + + // Update progress every 100ms + if elapsed.as_millis() > 100 { + let progress = self.bytes_read as f32 / self.total_size as f32; + + // Calculate estimated time remaining + let speed = self.bytes_read as f64 / elapsed.as_secs_f64(); + let remaining_bytes = self.total_size - self.bytes_read; + let eta = if speed > 0.0 { + Duration::from_secs_f64(remaining_bytes as f64 / speed) + } else { + Duration::from_secs(0) + }; + + (self.progress_callback)(progress, eta); + self.last_update = now; + } + + Ok(bytes) + } + } + + impl Seek for ProgressReader { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.inner.seek(pos) + } + } + + // Start extraction in blocking task + let cancel_rx = self.cancel_rx.clone(); + let progress_cb = Arc::new(Box::new(progress_callback) as Box); + let archive_path = archive_path.to_owned(); + let output_dir = output_dir.to_owned(); + + tokio::task::spawn_blocking(move || -> Result<()> { + let file = fs::File::open(&archive_path)?; + let file_size = file.metadata()?.len(); + + let progress_reader = ProgressReader { + inner: file, + bytes_read: 0, + total_size: file_size, + last_update: Instant::now(), + cancel_signal: cancel_rx, + progress_callback: progress_cb, + }; + + sevenz_rust::decompress(progress_reader, &output_dir)?; + Ok(()) + }).await??; + + Ok(()) + } +} + +impl Default for SevenZDearchiver { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/rust/src/installer/mod.rs b/rust/src/installer/mod.rs index 4994122..cf27816 100644 --- a/rust/src/installer/mod.rs +++ b/rust/src/installer/mod.rs @@ -1,4 +1,7 @@ pub mod downloader; pub mod extractor; pub mod validator; -pub mod shortcut; \ No newline at end of file +pub mod shortcut; +pub mod seven_z_dearchiver; + +pub use seven_z_dearchiver::SevenZDearchiver; \ No newline at end of file diff --git a/rust/src/installer/seven_z_dearchiver.rs b/rust/src/installer/seven_z_dearchiver.rs new file mode 100644 index 0000000..5af2319 --- /dev/null +++ b/rust/src/installer/seven_z_dearchiver.rs @@ -0,0 +1,112 @@ +use anyhow::Result; +use std::path::Path; +use std::sync::Arc; +use tokio::sync::{Mutex, watch}; +use std::io::{Read, Seek}; +use std::fs; +use std::time::{Duration, Instant}; + +pub struct SevenZDearchiver { + cancel_tx: watch::Sender, + cancel_rx: watch::Receiver, +} + +impl SevenZDearchiver { + pub fn new() -> Self { + let (cancel_tx, cancel_rx) = watch::channel(false); + Self { + cancel_tx, + cancel_rx, + } + } + + pub fn cancel(&self) { + let _ = self.cancel_tx.send(true); + } + + pub async fn extract(&self, archive_path: &Path, output_dir: &Path, progress_callback: F) -> Result<()> + where + F: Fn(f32, Duration) + Send + Sync + 'static + { + // Create wrapper for progress tracking + struct ProgressReader { + inner: R, + bytes_read: u64, + total_size: u64, + last_update: Instant, + cancel_signal: watch::Receiver, + progress_callback: Arc>, + } + + impl Read for ProgressReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // Check cancellation + if *self.cancel_signal.borrow() { + return Ok(0); // Return 0 to indicate EOF and stop extraction + } + + let bytes = self.inner.read(buf)?; + self.bytes_read += bytes as u64; + + let now = Instant::now(); + let elapsed = now.duration_since(self.last_update); + + // Update progress every 100ms + if elapsed.as_millis() > 100 { + let progress = self.bytes_read as f32 / self.total_size as f32; + + // Calculate estimated time remaining + let speed = self.bytes_read as f64 / elapsed.as_secs_f64(); + let remaining_bytes = self.total_size - self.bytes_read; + let eta = if speed > 0.0 { + Duration::from_secs_f64(remaining_bytes as f64 / speed) + } else { + Duration::from_secs(0) + }; + + (self.progress_callback)(progress, eta); + self.last_update = now; + } + + Ok(bytes) + } + } + + impl Seek for ProgressReader { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.inner.seek(pos) + } + } + + // Start extraction in blocking task + let cancel_rx = self.cancel_rx.clone(); + let progress_cb = Arc::new(Box::new(progress_callback) as Box); + let archive_path = archive_path.to_owned(); + let output_dir = output_dir.to_owned(); + + tokio::task::spawn_blocking(move || -> Result<()> { + let file = fs::File::open(&archive_path)?; + let file_size = file.metadata()?.len(); + + let progress_reader = ProgressReader { + inner: file, + bytes_read: 0, + total_size: file_size, + last_update: Instant::now(), + cancel_signal: cancel_rx, + progress_callback: progress_cb, + }; + + sevenz_rust::decompress(progress_reader, &output_dir)?; + Ok(()) + }).await??; + + Ok(()) + } +} + +impl Default for SevenZDearchiver { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file