Add unzip status

This commit is contained in:
Lncvrt committed 2026-03-10 20:35:26 -07:00
1 parent c65fb5e1a5
commit 0b4e639630
4 files changed
+63 -21

No files matched your search

+21 -8
View File
@@ -21,6 +21,7 @@ use tauri_plugin_prevent_default::Flags;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use tokio::{io::AsyncWriteExt, time::timeout}; use tokio::{io::AsyncWriteExt, time::timeout};
use zip::ZipArchive; use zip::ZipArchive;
use zip::result::ZipError;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@@ -46,20 +47,25 @@ fn should_skip(name: &str) -> bool {
|| name.ends_with(".DS_Store") || 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 res = tauri::async_runtime::spawn_blocking(move || {
let file = File::open(zip_path)?; let file = File::open(zip_path)?;
let mut archive = ZipArchive::new(BufReader::new(file))?; 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 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; 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() { if entry.is_dir() {
create_dir_all(&outpath)?; 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))?; 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>(()) Ok::<(), zip::result::ZipError>(())
@@ -362,7 +370,6 @@ async fn download(
} }
println!("[download] Hash verified OK"); println!("[download] Hash verified OK");
let _ = app.emit("download-finishing", format!("{}", &name));
println!("[download] Renaming .part -> .zip: {:?}", download_zip_path); println!("[download] Renaming .part -> .zip: {:?}", download_zip_path);
if let Err(e) = tokio::fs::rename(download_part_path.clone(), download_zip_path.clone()).await { 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)); 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); println!("[download] Removing zip file: {:?}", download_zip_path);
let _ = tokio::fs::remove_file(download_zip_path.clone()).await; let _ = tokio::fs::remove_file(download_zip_path.clone()).await;
@@ -391,7 +404,7 @@ async fn download(
println!( println!(
"[download] SUCCESS: '{}' downloaded, verified, and extracted", "[download] SUCCESS: '{}' downloaded, verified, and extracted",
name &name
); );
let _ = window.set_progress_bar(ProgressBarState { let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None), status: Some(ProgressBarStatus::None),
+25 -3
View File
@@ -235,13 +235,33 @@ export default function RootLayout ({
) )
unlisteners.push( unlisteners.push(
await listen<string>('download-finishing', event => { await listen<string>('unzip-start', event => {
const displayName = event.payload const displayName = event.payload
setDownloadProgress(prev => { setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === displayName) const i = prev.findIndex(d => d.version === displayName)
if (i === -1) return prev if (i === -1) return prev
const copy = [...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<string>('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 return copy
}) })
}) })
@@ -331,9 +351,11 @@ export default function RootLayout ({
false, false,
true, true,
false, false,
false,
0, 0,
0, 0,
0,
false,
0,
0 0
) )
) )
+13 -8
View File
@@ -52,15 +52,20 @@ export default function DownloadsPopup () {
Remove Remove
</button> </button>
</div> </div>
) : v.hash_checking || v.finishing ? ( ) : v.hash_checking ? (
<span <span className='text-blue-300 inline-block w-full text-center'>
className={`${ Verifying file integerty...
v.hash_checking ? 'text-blue-300' : 'text-green-300'
} inline-block w-full text-center`}
>
{v.hash_checking ? 'Verifying file integerty' : 'Finishing'}
...
</span> </span>
) : v.unzipping ? (
<div className='flex flex-col gap-1 w-full'>
<span className='text-center'>
Unzipped {v.unzipped} / {v.unzipTotal} files
</span>
<ProgressBar
progress={(v.unzipped / v.unzipTotal) * 100}
className='w-full'
/>
</div>
) : ( ) : (
<div className='flex flex-col gap-1 w-full'> <div className='flex flex-col gap-1 w-full'>
<span className='text-center'> <span className='text-center'>
+4 -2
View File
@@ -6,9 +6,11 @@ export class DownloadProgress {
public failed: boolean, public failed: boolean,
public queued: boolean, public queued: boolean,
public hash_checking: boolean, public hash_checking: boolean,
public finishing: boolean,
public speed: number, public speed: number,
public size: number, public size: number,
public etaSecs: number public etaSecs: number,
public unzipping: boolean,
public unzipTotal: number,
public unzipped: number
) {} ) {}
} }