diff --git a/Cargo.lock b/Cargo.lock index 1df877c..a1caf80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,7 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-http", "tauri-plugin-opener", + "zip", ] [[package]] diff --git a/js-server/scripts/supabase-download.ts b/js-server/scripts/supabase-download.ts index 6885e18..b784639 100644 --- a/js-server/scripts/supabase-download.ts +++ b/js-server/scripts/supabase-download.ts @@ -11,7 +11,10 @@ async function main() { const { data, error } = await supabase.storage.from("yomitan").list("ja"); if (error) throw error; if (data) { - console.log(data.map((d) => d.id)); + console.log(data); + // no sha256, but may be precomputed and manually placed at data[*].metadata.* + // data[*].metadata.{size,contentLength} could be meaningful + // GitHub releases uses digest: 'sha256:*' in assets[*].digest currently const toDownload = data.filter(() => true); diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a8639b0..b707c6f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,3 +26,4 @@ anyhow = "1.0.102" tauri-plugin-fs = "2" serde_json = "1.0.149" serde = "1.0.228" +zip = "8.1.0" diff --git a/src-tauri/src/command.rs b/src-tauri/src/command.rs index 1e8d7b4..2ebac38 100644 --- a/src-tauri/src/command.rs +++ b/src-tauri/src/command.rs @@ -1,9 +1,14 @@ -use std::{fs::File, io::Write, path::PathBuf}; +use std::{ + fs::{File, remove_file}, + io::Write, + path::PathBuf, +}; use cjdic2_core::{AppService, CJDicError, SqlParam, YomitanRow, YomitanZipImportResult}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter, Manager}; use tauri_plugin_http::reqwest::Client; +use zip::ZipArchive; #[derive(Deserialize)] pub struct YomitanDictEntry { @@ -27,18 +32,21 @@ pub async fn init_yomitan( .map_err(|e| CJDicError::Error(e.to_string()))?; for d in dicts.iter().clone() { - if !app_dir.join(&d.filepath).exists() { - download_url_local( - d.url.clone(), - app_dir.clone(), - d.filepath.clone(), - |progress| { - app.emit("download-url-progress", progress).unwrap(); - }, - ) - .await - .map_err(|e| CJDicError::Error(e.to_string()))?; + let zip_path = app_dir.join(&d.filepath); + if zip_path.exists() && File::open(zip_path).is_ok_and(|f| ZipArchive::new(f).is_ok()) { + continue; } + + download_url_local( + d.url.clone(), + app_dir.clone(), + d.filepath.clone(), + |progress| { + app.emit("download-url-progress", progress).unwrap(); + }, + ) + .await + .map_err(|e| CJDicError::Error(e.to_string()))?; } let zip_files: Vec = dicts @@ -155,8 +163,6 @@ where let content_length = response.content_length().unwrap_or(0); let mut file = File::create(&file_pf).map_err(|e| CJDicError::Error(e.to_string()))?; - println!("{}", content_length); - // Downloading the file in chunks let mut downloaded: u64 = 0; while let Some(chunk) = response @@ -174,6 +180,18 @@ where downloaded, }); } + + // Verify download completeness + if content_length > 0 && downloaded < content_length { + // Delete incomplete file + drop(file); // ensure file handle is released before deleting + remove_file(&file_pf).ok(); + return Err(CJDicError::Error(format!( + "Incomplete download: expected {} bytes, got {}", + content_length, downloaded + ))); + } + println!("File downloaded successfully to {:?}", &file_pf); } else { eprintln!("Failed to download file: {:?}", response.status());