From 047054f8f7353b960b614d86b2d1803fdfcaa557 Mon Sep 17 00:00:00 2001 From: polv Date: Wed, 4 Mar 2026 13:19:59 +0700 Subject: [PATCH] fix downloader, no bundle --- src-core/src/service.rs | 5 +- src-react/src/components/LoadingDialog.tsx | 131 ++++++---------- src-react/src/pages/SearchPage.tsx | 13 -- src-tauri/src/command.rs | 171 ++++++++++----------- 4 files changed, 129 insertions(+), 191 deletions(-) diff --git a/src-core/src/service.rs b/src-core/src/service.rs index 961e7de..a97fed8 100644 --- a/src-core/src/service.rs +++ b/src-core/src/service.rs @@ -192,7 +192,10 @@ impl AppService { { let conn = Connection::open(self.db.dir.join(YOMITAN_DBFILE))?; for z in to_be_removed_dicts.clone().iter() { - conn.execute("DELETE FROM dictionaries WHERE bundle_name = ?1", [z])?; + conn.execute( + "DELETE FROM dictionaries WHERE bundle_name = ?1 AND lang = ?2", + [z, lang], + )?; } } diff --git a/src-react/src/components/LoadingDialog.tsx b/src-react/src/components/LoadingDialog.tsx index 8fac9c8..dd92d9e 100644 --- a/src-react/src/components/LoadingDialog.tsx +++ b/src-react/src/components/LoadingDialog.tsx @@ -3,11 +3,14 @@ import { useEffect, useRef, useState } from "react"; import { Dialog, Portal } from "@chakra-ui/react"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; -import { BaseDirectory, exists } from "@tauri-apps/plugin-fs"; -import { fetch } from "@tauri-apps/plugin-http"; import { supabase } from "../lib/supabaseClient"; +interface YomitanDictEntry { + url: string; + filepath: string; +} + interface LoadYomitanZipDirResult { new_dicts: string[]; to_be_removed_dicts: string[]; @@ -44,99 +47,55 @@ function LoadingDialog() { downloadAssets(); async function downloadAssets(lang = "ja") { - const new_dicts = new Set(); - // const to_be_removed_dicts = new Set(); + const dicts: YomitanDictEntry[] = []; if (lang === "ja") { - await getPixiv().catch((e) => { - console.error(e); - }); - async function getPixiv() { + { const filename = "Pixiv.zip"; - new_dicts.add(filename); - const filepath = `yomitan/${lang}/${filename}`; - if ( - !(await exists(filepath, { - baseDir: BaseDirectory.AppData, - })) - ) { - const publicUrl = await ghLatestReleaseURL( - "MarvNC/pixiv-yomitan", - /^Pixiv_.+\.zip$/, - ); - if (publicUrl) { - await invoke("download_url", { url: publicUrl, filepath }); - } + dicts.push({ + filepath, + url: + (await ghLatestReleaseURL( + "MarvNC/pixiv-yomitan", + /^Pixiv_.+\.zip$/, + ).catch((e) => { + console.error(e); + return ""; + })) || "", + }); + } + + { + const { data, error } = await supabase.storage + .from("yomitan") + .list(lang); + if (error) throw error; + + for (const d of data || []) { + const filepath = `yomitan/${lang}/${d.name}`; + + dicts.push({ + filepath, + url: supabase.storage + .from("yomitan") + .getPublicUrl(`${lang}/${d.name}`).data.publicUrl, + }); } } } - let isSupabase = true; - await getSupabase().catch((e) => { - console.error(e); - isSupabase = false; - }); - async function getSupabase() { - const { data, error } = await supabase.storage - .from("yomitan") - .list(lang); - if (error) throw error; - - for (const d of data || []) { - new_dicts.add(d.name); - const filepath = `yomitan/${lang}/${d.name}`; - if ( - !(await exists(filepath, { - baseDir: BaseDirectory.AppData, - })) - ) { - const { - data: { publicUrl }, - } = supabase.storage - .from("yomitan") - .getPublicUrl(`${lang}/${d.name}`); - - await invoke("download_url", { url: publicUrl, filepath }); - } - } - } - - if (isSupabase) { - // console.log([...new_dicts], [...to_be_removed_dicts]); - // for (const f of await readDir(`yomitan/${lang}`, { - // baseDir: BaseDirectory.AppData, - // })) { - // if (f.isFile && f.name.endsWith(".zip")) { - // if (new_dicts.has(f.name)) { - // new_dicts.delete(f.name); - // } else { - // to_be_removed_dicts.add(f.name); - // } - // } - // } - // for (const bundleName of Array.from(new_dicts).sort()) { - // updateProgress({ - // message: `Importing ${bundleName}`, - // }); - // await invoke("import_yomitan_dict", { - // bundleName, - // lang, - // }); - // } - // for (const bundleName of Array.from(to_be_removed_dicts).sort()) { - // updateProgress({ - // message: `Removing old ${bundleName}`, - // }); - // await invoke("remove_yomitan_dict", { - // bundleName, - // lang, - // }); - // } - } - + await invoke("init_yomitan", { dicts, lang }); setMessages([]); + + // empty query warmup + await invoke("search_yomitan", { + qTerm: "", + qReading: "", + limit: 1, + offset: 0, + }); } listen("load-yomitan-dir", ({ payload }) => { @@ -144,7 +103,7 @@ function LoadingDialog() { if (payload.new_dicts.length > 0) { messages = [ ...messages, - { message: "Importing Yomitan zip from the app bundle:" }, + { message: "Importing Yomitan zip:" }, ...payload.new_dicts.map((d) => ({ message: `- ${d}` })), ]; } diff --git a/src-react/src/pages/SearchPage.tsx b/src-react/src/pages/SearchPage.tsx index 4e6b168..a3d0cb4 100644 --- a/src-react/src/pages/SearchPage.tsx +++ b/src-react/src/pages/SearchPage.tsx @@ -45,19 +45,6 @@ function SearchPage() { const lang = "ja-JP"; - const isInit = useRef(false); - useEffect(() => { - if (isInit.current) return; - isInit.current = true; - - invoke("search_yomitan", { - qTerm: "", - qReading: "", - limit: 1, - offset: 0, - }); - }); - useEffect(() => { trySearch(); }, [q]); diff --git a/src-tauri/src/command.rs b/src-tauri/src/command.rs index eb56231..b0aff56 100644 --- a/src-tauri/src/command.rs +++ b/src-tauri/src/command.rs @@ -1,95 +1,69 @@ -use std::{borrow::Cow, fs::File, io::Write, path::PathBuf}; +use std::{fs::File, io::Write, path::PathBuf}; -use cjdic2_core::{ - AppService, CJDicError, SqlParam, YomitanRow, YomitanZipImportResult, ZipSource, -}; +use cjdic2_core::{AppService, CJDicError, SqlParam, YomitanRow, YomitanZipImportResult}; use reqwest::Client; -use serde::Serialize; -use tauri::{AppHandle, Emitter, Manager, Runtime, path::BaseDirectory}; -use tauri_plugin_fs::FsExt; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter, Manager}; -pub struct BundledZip { - pub name: String, - pub path: PathBuf, // resolved asset://localhost/... path on Android - pub app: AppHandle, -} - -impl BundledZip { - pub fn new(app: AppHandle, relative_path: &str) -> Result { - let name = relative_path - .split('/') - .last() - .unwrap_or(relative_path) - .to_string(); - - let path = app.path().resolve(relative_path, BaseDirectory::Resource)?; - - Ok(Self { name, path, app }) - } -} - -impl ZipSource for BundledZip { - fn file_name(&self) -> &str { - &self.name - } - - fn bytes(&self) -> std::io::Result> { - self.app - .fs() - .read(&self.path) // PathBuf implements Into - .map(Cow::Owned) - } +#[derive(Deserialize)] +pub struct YomitanDictEntry { + url: String, + filepath: String, } #[tauri::command] pub async fn init_yomitan( app: AppHandle, + dicts: Vec, + lang: String, state: tauri::State<'_, AppService>, ) -> Result<(), CJDicError> { let mut new_dicts: Vec = vec![]; let mut to_be_removed_dicts: Vec = vec![]; - for lang in vec!["ja"] { - let folder_name = format!("resources/yomitan/{lang}"); - let folder_name = folder_name.as_str(); + let app_dir = app + .path() + .app_data_dir() + .map_err(|e| CJDicError::Error(e.to_string()))?; - let zip_dir = app - .path() - .resolve(folder_name, tauri::path::BaseDirectory::Resource) + 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()))?; - - // Manifest is needed, as Android bundled fs can't read_dir - let files: Vec = - serde_json::from_str(&app.fs().read_to_string(zip_dir.join("manifest.json"))?)?; - - let zip_files = files - .iter() - .map(|f| BundledZip::new(app.clone(), &format!("{folder_name}/{f}"))) - .collect::, _>>() - .map_err(|e| CJDicError::Error(e.to_string()))?; - - if zip_files.is_empty() { - continue; } - - let mut r = state.load_yomitan_zip_dir( - zip_files, - lang, - |r| { - app.emit("load-yomitan-dir", r).unwrap(); - }, - |r| { - app.emit("yomitan-import-progress", r).unwrap(); - }, - )?; - - new_dicts.append(&mut r.new_dicts); - to_be_removed_dicts.append(&mut r.to_be_removed_dicts); } + let zip_files: Vec = dicts + .into_iter() + .map(|d| app_dir.join(&d.filepath)) + .collect(); + + let mut r = state.load_yomitan_zip_dir( + zip_files, + lang.as_str(), + |r| { + app.emit("load-yomitan-dir", r).unwrap(); + }, + |r| { + app.emit("yomitan-import-progress", r).unwrap(); + }, + )?; + + new_dicts.append(&mut r.new_dicts); + to_be_removed_dicts.append(&mut r.to_be_removed_dicts); + if new_dicts.len() + to_be_removed_dicts.len() > 0 { - // Cleanup doesn't appear to help even after app restart - // state.cleanup_yomitan_writer()?; + println!("Imported {:?}", new_dicts); + println!("Removed {:?}", to_be_removed_dicts); + println!("Request app restart?",); // Request app restart anyway app.request_restart(); @@ -157,17 +131,16 @@ struct DownloadProgress { downloaded: u64, } -#[tauri::command] -pub async fn download_url( - app: AppHandle, +async fn download_url_local( url: String, + data_dir: PathBuf, filepath: String, -) -> Result { + callback: Callback, +) -> Result +where + Callback: Fn(DownloadProgress), +{ let client = Client::new(); - let data_dir = app - .path() - .app_data_dir() - .map_err(|e| CJDicError::Error(e.to_string()))?; let file_pf = data_dir.join(filepath.as_str()); // Making the asynchronous GET request @@ -192,16 +165,12 @@ pub async fn download_url( file.write_all(&chunk)?; downloaded += chunk.len() as u64; - app.emit( - "download-url-progress", - DownloadProgress { - url: url.clone(), - filepath: filepath.clone(), - content_length, - downloaded, - }, - ) - .unwrap(); + callback(DownloadProgress { + url: url.clone(), + filepath: filepath.clone(), + content_length, + downloaded, + }); } println!("File downloaded successfully to {:?}", &file_pf); } else { @@ -210,3 +179,23 @@ pub async fn download_url( Ok(true) } + +#[tauri::command] +pub async fn download_url( + app: AppHandle, + url: String, + filepath: String, +) -> Result { + download_url_local( + url, + app.path() + .app_data_dir() + .map_err(|e| CJDicError::Error(e.to_string()))?, + filepath, + |progress| { + app.emit("download-url-progress", progress).unwrap(); + }, + ) + .await + .map_err(|e| CJDicError::Error(e.to_string())) +}