Add unzip status
This commit is contained in:
4 files changed
+63
-21
No files matched your search
+21
-8
@@ -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
@@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user