diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 61f9b4e..6a1e4ec 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -21,6 +21,7 @@ use tauri_plugin_prevent_default::Flags; use tokio::io::AsyncReadExt; use tokio::{io::AsyncWriteExt, time::timeout}; use zip::ZipArchive; +use zip::result::ZipError; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -46,20 +47,25 @@ fn should_skip(name: &str) -> bool { || name.ends_with(".DS_Store") } -async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String { +async fn unzip_to_dir(app: AppHandle, zip_path: PathBuf, out_dir: PathBuf, name: String) -> String { + let _ = app.emit("unzip-start", format!("{}", name)); let res = tauri::async_runtime::spawn_blocking(move || { let file = File::open(zip_path)?; let mut archive = ZipArchive::new(BufReader::new(file))?; + let total = archive.len(); - for i in 0..archive.len() { + for i in 0..total { let mut entry = archive.by_index(i)?; - let name = entry.name(); + let entry_name = entry.name(); - if should_skip(name) { + if should_skip(entry_name) { continue; } - let outpath = out_dir.join(name); + let outpath = out_dir.join(entry_name); + if !outpath.starts_with(&out_dir) { + return Err(ZipError::InvalidArchive("Path traversal detected".into())); + } if entry.is_dir() { create_dir_all(&outpath)?; @@ -81,6 +87,8 @@ async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String { std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?; } } + + let _ = app.emit("unzip-progress", format!("{}:{}:{}", name, i + 1, total)); } Ok::<(), zip::result::ZipError>(()) @@ -362,7 +370,6 @@ async fn download( } println!("[download] Hash verified OK"); - let _ = app.emit("download-finishing", format!("{}", &name)); println!("[download] Renaming .part -> .zip: {:?}", download_zip_path); if let Err(e) = tokio::fs::rename(download_part_path.clone(), download_zip_path.clone()).await { @@ -375,7 +382,13 @@ async fn download( } println!("[download] Unzipping to: {:?}", game_path.join(&name)); - let unzip_res = unzip_to_dir(download_zip_path.clone(), game_path.join(&name)).await; + let unzip_res = unzip_to_dir( + app, + download_zip_path.clone(), + game_path.join(&name), + name.clone(), + ) + .await; println!("[download] Removing zip file: {:?}", download_zip_path); let _ = tokio::fs::remove_file(download_zip_path.clone()).await; @@ -391,7 +404,7 @@ async fn download( println!( "[download] SUCCESS: '{}' downloaded, verified, and extracted", - name + &name ); let _ = window.set_progress_bar(ProgressBarState { status: Some(ProgressBarStatus::None), diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bb28c6f..c687c64 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -235,13 +235,33 @@ export default function RootLayout ({ ) unlisteners.push( - await listen('download-finishing', event => { + await listen('unzip-start', event => { const displayName = event.payload setDownloadProgress(prev => { const i = prev.findIndex(d => d.version === displayName) if (i === -1) return prev const copy = [...prev] - copy[i] = { ...copy[i], hash_checking: false, finishing: true } + copy[i] = { ...copy[i], hash_checking: false, unzipping: true } + return copy + }) + }) + ) + + unlisteners.push( + await listen('unzip-progress', async event => { + const [displayName, unzippedStr, unzipTotalStr] = + event.payload.split(':') + const unzipped = Number(unzippedStr) + const unzipTotal = Number(unzipTotalStr) + setDownloadProgress(prev => { + const i = prev.findIndex(d => d.version === displayName) + if (i === -1) return prev + const copy = [...prev] + copy[i] = { + ...copy[i], + unzipped, + unzipTotal + } return copy }) }) @@ -331,9 +351,11 @@ export default function RootLayout ({ false, true, false, - false, 0, 0, + 0, + false, + 0, 0 ) ) diff --git a/src/componets/popups/Downloads.tsx b/src/componets/popups/Downloads.tsx index 5db1598..fdcd0b0 100644 --- a/src/componets/popups/Downloads.tsx +++ b/src/componets/popups/Downloads.tsx @@ -52,15 +52,20 @@ export default function DownloadsPopup () { Remove - ) : v.hash_checking || v.finishing ? ( - - {v.hash_checking ? 'Verifying file integerty' : 'Finishing'} - ... + ) : v.hash_checking ? ( + + Verifying file integerty... + ) : v.unzipping ? ( +
+ + Unzipped {v.unzipped} / {v.unzipTotal} files + + +
) : (
diff --git a/src/types/DownloadProgress.ts b/src/types/DownloadProgress.ts index eeafb03..bd75042 100644 --- a/src/types/DownloadProgress.ts +++ b/src/types/DownloadProgress.ts @@ -6,9 +6,11 @@ export class DownloadProgress { public failed: boolean, public queued: boolean, public hash_checking: boolean, - public finishing: boolean, public speed: number, public size: number, - public etaSecs: number + public etaSecs: number, + public unzipping: boolean, + public unzipTotal: number, + public unzipped: number ) {} }