fix downloader, no bundle

This commit is contained in:
polv committed 2026-03-04 13:19:59 +07:00
1 parent 5380742513
commit 047054f8f7
4 files changed
+129 -191

No files matched your search

+4 -1
View File
@@ -192,7 +192,10 @@ impl AppService {
{ {
let conn = Connection::open(self.db.dir.join(YOMITAN_DBFILE))?; let conn = Connection::open(self.db.dir.join(YOMITAN_DBFILE))?;
for z in to_be_removed_dicts.clone().iter() { 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],
)?;
} }
} }
+45 -86
View File
@@ -3,11 +3,14 @@ import { useEffect, useRef, useState } from "react";
import { Dialog, Portal } from "@chakra-ui/react"; import { Dialog, Portal } from "@chakra-ui/react";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; 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"; import { supabase } from "../lib/supabaseClient";
interface YomitanDictEntry {
url: string;
filepath: string;
}
interface LoadYomitanZipDirResult { interface LoadYomitanZipDirResult {
new_dicts: string[]; new_dicts: string[];
to_be_removed_dicts: string[]; to_be_removed_dicts: string[];
@@ -44,99 +47,55 @@ function LoadingDialog() {
downloadAssets(); downloadAssets();
async function downloadAssets(lang = "ja") { async function downloadAssets(lang = "ja") {
const new_dicts = new Set<string>(); const dicts: YomitanDictEntry[] = [];
// const to_be_removed_dicts = new Set<string>();
if (lang === "ja") { if (lang === "ja") {
await getPixiv().catch((e) => { {
console.error(e);
});
async function getPixiv() {
const filename = "Pixiv.zip"; const filename = "Pixiv.zip";
new_dicts.add(filename);
const filepath = `yomitan/${lang}/${filename}`; const filepath = `yomitan/${lang}/${filename}`;
if (
!(await exists(filepath, {
baseDir: BaseDirectory.AppData,
}))
) {
const publicUrl = await ghLatestReleaseURL(
"MarvNC/pixiv-yomitan",
/^Pixiv_.+\.zip$/,
);
if (publicUrl) { dicts.push({
await invoke("download_url", { url: publicUrl, filepath }); 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 invoke("init_yomitan", { dicts, lang });
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,
// });
// }
}
setMessages([]); setMessages([]);
// empty query warmup
await invoke("search_yomitan", {
qTerm: "",
qReading: "",
limit: 1,
offset: 0,
});
} }
listen<LoadYomitanZipDirResult>("load-yomitan-dir", ({ payload }) => { listen<LoadYomitanZipDirResult>("load-yomitan-dir", ({ payload }) => {
@@ -144,7 +103,7 @@ function LoadingDialog() {
if (payload.new_dicts.length > 0) { if (payload.new_dicts.length > 0) {
messages = [ messages = [
...messages, ...messages,
{ message: "Importing Yomitan zip from the app bundle:" }, { message: "Importing Yomitan zip:" },
...payload.new_dicts.map((d) => ({ message: `- ${d}` })), ...payload.new_dicts.map((d) => ({ message: `- ${d}` })),
]; ];
} }
-13
View File
@@ -45,19 +45,6 @@ function SearchPage() {
const lang = "ja-JP"; 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(() => { useEffect(() => {
trySearch(); trySearch();
}, [q]); }, [q]);
+80 -91
View File
@@ -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::{ use cjdic2_core::{AppService, CJDicError, SqlParam, YomitanRow, YomitanZipImportResult};
AppService, CJDicError, SqlParam, YomitanRow, YomitanZipImportResult, ZipSource,
};
use reqwest::Client; use reqwest::Client;
use serde::Serialize; use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter, Manager, Runtime, path::BaseDirectory}; use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_fs::FsExt;
pub struct BundledZip<R: Runtime> { #[derive(Deserialize)]
pub name: String, pub struct YomitanDictEntry {
pub path: PathBuf, // resolved asset://localhost/... path on Android url: String,
pub app: AppHandle<R>, filepath: String,
}
impl<R: Runtime> BundledZip<R> {
pub fn new(app: AppHandle<R>, relative_path: &str) -> Result<Self, tauri::Error> {
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<R: Runtime> ZipSource for BundledZip<R> {
fn file_name(&self) -> &str {
&self.name
}
fn bytes(&self) -> std::io::Result<Cow<'_, [u8]>> {
self.app
.fs()
.read(&self.path) // PathBuf implements Into<FilePath>
.map(Cow::Owned)
}
} }
#[tauri::command] #[tauri::command]
pub async fn init_yomitan( pub async fn init_yomitan(
app: AppHandle, app: AppHandle,
dicts: Vec<YomitanDictEntry>,
lang: String,
state: tauri::State<'_, AppService>, state: tauri::State<'_, AppService>,
) -> Result<(), CJDicError> { ) -> Result<(), CJDicError> {
let mut new_dicts: Vec<String> = vec![]; let mut new_dicts: Vec<String> = vec![];
let mut to_be_removed_dicts: Vec<String> = vec![]; let mut to_be_removed_dicts: Vec<String> = vec![];
for lang in vec!["ja"] { let app_dir = app
let folder_name = format!("resources/yomitan/{lang}"); .path()
let folder_name = folder_name.as_str(); .app_data_dir()
.map_err(|e| CJDicError::Error(e.to_string()))?;
let zip_dir = app for d in dicts.iter().clone() {
.path() if !app_dir.join(&d.filepath).exists() {
.resolve(folder_name, tauri::path::BaseDirectory::Resource) 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()))?; .map_err(|e| CJDicError::Error(e.to_string()))?;
// Manifest is needed, as Android bundled fs can't read_dir
let files: Vec<String> =
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::<Result<Vec<_>, _>>()
.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<PathBuf> = 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 { if new_dicts.len() + to_be_removed_dicts.len() > 0 {
// Cleanup doesn't appear to help even after app restart println!("Imported {:?}", new_dicts);
// state.cleanup_yomitan_writer()?; println!("Removed {:?}", to_be_removed_dicts);
println!("Request app restart?",);
// Request app restart anyway // Request app restart anyway
app.request_restart(); app.request_restart();
@@ -157,17 +131,16 @@ struct DownloadProgress {
downloaded: u64, downloaded: u64,
} }
#[tauri::command] async fn download_url_local<Callback>(
pub async fn download_url(
app: AppHandle,
url: String, url: String,
data_dir: PathBuf,
filepath: String, filepath: String,
) -> Result<bool, CJDicError> { callback: Callback,
) -> Result<bool, CJDicError>
where
Callback: Fn(DownloadProgress),
{
let client = Client::new(); 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()); let file_pf = data_dir.join(filepath.as_str());
// Making the asynchronous GET request // Making the asynchronous GET request
@@ -192,16 +165,12 @@ pub async fn download_url(
file.write_all(&chunk)?; file.write_all(&chunk)?;
downloaded += chunk.len() as u64; downloaded += chunk.len() as u64;
app.emit( callback(DownloadProgress {
"download-url-progress", url: url.clone(),
DownloadProgress { filepath: filepath.clone(),
url: url.clone(), content_length,
filepath: filepath.clone(), downloaded,
content_length, });
downloaded,
},
)
.unwrap();
} }
println!("File downloaded successfully to {:?}", &file_pf); println!("File downloaded successfully to {:?}", &file_pf);
} else { } else {
@@ -210,3 +179,23 @@ pub async fn download_url(
Ok(true) Ok(true)
} }
#[tauri::command]
pub async fn download_url(
app: AppHandle,
url: String,
filepath: String,
) -> Result<bool, CJDicError> {
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()))
}