mirror of
https://github.com/patarapolw/cjdic2.git
synced 2026-11-09 17:39:35 +00:00
extract image assets for yomitan zip
This commit is contained in:
1 parent
79deaf9372
commit
52c25992ab
11 files changed
+114
-52
No files matched your search
Generated
+7
@@ -1981,6 +1981,12 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@@ -4837,6 +4843,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"http-range",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
println!("{:?}", p);
|
||||
})?;
|
||||
|
||||
let zip_dir = Path::new("src-tauri/resources/yomitan");
|
||||
let zip_dir = Path::new("tmp/yomitan");
|
||||
println!("zip_dir: {:?}", absolute(zip_dir)); // Relative to workspace root
|
||||
|
||||
for entry in
|
||||
@@ -31,9 +31,15 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
|
||||
println!(
|
||||
"{:?}",
|
||||
AppService::import_yomitan_zip_file(&mut writer, &p, "ja", |progress| {
|
||||
println!("{:?}", progress);
|
||||
})?,
|
||||
AppService::import_yomitan_zip_file(
|
||||
&mut writer,
|
||||
&p,
|
||||
&zip_dir.to_path_buf(),
|
||||
"ja",
|
||||
|progress| {
|
||||
println!("{:?}", progress);
|
||||
}
|
||||
)?,
|
||||
);
|
||||
} else {
|
||||
println!("not zip_file: {:?}", p);
|
||||
|
||||
@@ -7,7 +7,8 @@ use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
env::current_dir,
|
||||
io::{Cursor, Read},
|
||||
fs::{File, create_dir_all},
|
||||
io::{self, Cursor, Read},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use zip::ZipArchive;
|
||||
@@ -437,6 +438,7 @@ impl YomitanWriter {
|
||||
pub fn import_dictionary_zip_file<Z, Callback>(
|
||||
&mut self,
|
||||
zip_file: &Z,
|
||||
asset_dir: &PathBuf,
|
||||
lang: &str,
|
||||
progress_callback: Callback,
|
||||
) -> anyhow::Result<YomitanZipImportResult, CJDicError>
|
||||
@@ -452,6 +454,27 @@ impl YomitanWriter {
|
||||
let cursor = Cursor::new(zip_file.bytes()?);
|
||||
let mut archive = ZipArchive::new(cursor)?;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
// Metadata — borrow ends before by_index_raw
|
||||
let (name, is_dir) = {
|
||||
let f = archive.by_index(i)?;
|
||||
(f.name().to_owned(), f.is_dir())
|
||||
};
|
||||
|
||||
if !name.starts_with("assets") || is_dir {
|
||||
continue;
|
||||
}
|
||||
|
||||
let out_path = asset_dir.join(name);
|
||||
if let Some(parent) = out_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let mut entry = archive.by_index(i)?;
|
||||
let mut out = File::create(&out_path)?;
|
||||
io::copy(&mut entry, &mut out)?; // writes plain bytes
|
||||
}
|
||||
|
||||
let index_file = match archive.by_name("index.json") {
|
||||
Ok(mut f) => {
|
||||
let mut s = String::new();
|
||||
|
||||
+11
-3
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use rusqlite::{Connection, params_from_iter};
|
||||
@@ -166,6 +166,7 @@ impl AppService {
|
||||
pub fn load_yomitan_zip_dir<Z, LoadCallback, ImportCallback>(
|
||||
&self,
|
||||
zip_dir: Vec<Z>,
|
||||
asset_dir: &PathBuf,
|
||||
lang: &str,
|
||||
load_callback: LoadCallback,
|
||||
import_callback: ImportCallback,
|
||||
@@ -246,7 +247,13 @@ impl AppService {
|
||||
let mut writer = YomitanWriter::new(self.db.dir.clone())?;
|
||||
for z in new_dicts.clone().iter() {
|
||||
if let Some(zip_file) = dir_zip_map.get(z) {
|
||||
Self::import_yomitan_zip_file(&mut writer, *zip_file, lang, &import_callback)?;
|
||||
Self::import_yomitan_zip_file(
|
||||
&mut writer,
|
||||
*zip_file,
|
||||
&asset_dir,
|
||||
lang,
|
||||
&import_callback,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,6 +275,7 @@ impl AppService {
|
||||
pub fn import_yomitan_zip_file<Z, Callback>(
|
||||
writer: &mut YomitanWriter, // Reuse existing connection
|
||||
zip_file: &Z,
|
||||
asset_dir: &PathBuf,
|
||||
lang: &str,
|
||||
progress_callback: Callback,
|
||||
) -> Result<YomitanZipImportResult, CJDicError>
|
||||
@@ -275,7 +283,7 @@ impl AppService {
|
||||
Z: ZipSource,
|
||||
Callback: Fn(YomitanProgress),
|
||||
{
|
||||
Ok(writer.import_dictionary_zip_file(zip_file, lang, progress_callback)?)
|
||||
Ok(writer.import_dictionary_zip_file(zip_file, asset_dir, lang, progress_callback)?)
|
||||
}
|
||||
|
||||
pub fn remove_yomitan_dictionary(
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 254 B |
@@ -2,10 +2,12 @@ import { createElement as h } from "react";
|
||||
|
||||
export default function Glossary({
|
||||
glossary,
|
||||
yomitanURL,
|
||||
maxLength,
|
||||
onTermClicked,
|
||||
}: {
|
||||
glossary: any;
|
||||
yomitanURL: string;
|
||||
maxLength?: number;
|
||||
onTermClicked: (t: string) => void;
|
||||
}) {
|
||||
@@ -29,7 +31,7 @@ export default function Glossary({
|
||||
|
||||
// GlossaryImage
|
||||
if ("type" in glossary && glossary.type === "image") {
|
||||
return <GlossaryImage {...glossary} />;
|
||||
return <GlossaryImage img={glossary} yomitanURL={yomitanURL} />;
|
||||
}
|
||||
|
||||
// GlossaryStructuredContent
|
||||
@@ -38,8 +40,7 @@ export default function Glossary({
|
||||
return (
|
||||
<StructuredContent
|
||||
node={sc.content}
|
||||
maxLength={maxLength}
|
||||
onTermClicked={onTermClicked}
|
||||
{...{ yomitanURL, maxLength, onTermClicked }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -53,10 +54,10 @@ export default function Glossary({
|
||||
return null;
|
||||
}
|
||||
|
||||
function GlossaryImage(img: any) {
|
||||
function GlossaryImage({ img, yomitanURL }: { img: any; yomitanURL: string }) {
|
||||
return (
|
||||
<img
|
||||
src={img.path}
|
||||
src={`${yomitanURL || ""}/${img.path}`}
|
||||
width={img.width}
|
||||
height={img.height}
|
||||
title={img.title}
|
||||
@@ -72,10 +73,12 @@ function GlossaryImage(img: any) {
|
||||
|
||||
function StructuredContent({
|
||||
node,
|
||||
yomitanURL,
|
||||
maxLength,
|
||||
onTermClicked,
|
||||
}: {
|
||||
node: any;
|
||||
yomitanURL: string;
|
||||
maxLength?: number;
|
||||
onTermClicked: (t: string) => void;
|
||||
}): any {
|
||||
@@ -100,8 +103,7 @@ function StructuredContent({
|
||||
<StructuredContent
|
||||
key={i}
|
||||
node={n}
|
||||
maxLength={maxLength}
|
||||
onTermClicked={onTermClicked}
|
||||
{...{ yomitanURL, maxLength, onTermClicked }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
@@ -118,7 +120,7 @@ function StructuredContent({
|
||||
|
||||
// Image
|
||||
if (tag === "img") {
|
||||
return <GlossaryImage {...node} />;
|
||||
return <GlossaryImage img={node} yomitanURL={yomitanURL} />;
|
||||
}
|
||||
|
||||
// Link
|
||||
@@ -145,8 +147,7 @@ function StructuredContent({
|
||||
{content ? (
|
||||
<StructuredContent
|
||||
node={content}
|
||||
maxLength={maxLength}
|
||||
onTermClicked={onTermClicked}
|
||||
{...{ yomitanURL, maxLength, onTermClicked }}
|
||||
/>
|
||||
) : null}
|
||||
</a>
|
||||
@@ -159,6 +160,11 @@ function StructuredContent({
|
||||
return h(
|
||||
tag,
|
||||
{ style: block.style, title: block.title, open: block.open },
|
||||
StructuredContent({ node: block.content, maxLength, onTermClicked }),
|
||||
StructuredContent({
|
||||
node: block.content,
|
||||
yomitanURL,
|
||||
maxLength,
|
||||
onTermClicked,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -79,9 +79,9 @@ function LoadingDialog() {
|
||||
const dicts: YomitanDownloadDictEntry[] = [];
|
||||
|
||||
if (lang === "ja") {
|
||||
const filename = "Pixiv.zip";
|
||||
const filepath = `yomitan/${lang}/${filename}`;
|
||||
const existing = existing_dicts.find((d) => d.bundle_name === filename);
|
||||
var filename = "PixivLight.zip";
|
||||
var filepath = `yomitan/${lang}/${filename}`;
|
||||
var existing = existing_dicts.find((d) => d.bundle_name === filename);
|
||||
if (!existing) {
|
||||
// Maybe check revision update
|
||||
dicts.push({
|
||||
@@ -89,7 +89,7 @@ function LoadingDialog() {
|
||||
url:
|
||||
(await ghLatestReleaseURL(
|
||||
"MarvNC/pixiv-yomitan",
|
||||
/^Pixiv_.+\.zip$/,
|
||||
/^PixivLight_.+\.zip$/,
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
return "";
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
Stack,
|
||||
Switch,
|
||||
} from "@chakra-ui/react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import { appDataDir, join } from "@tauri-apps/api/path";
|
||||
import { readText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
|
||||
import Glossary from "../components/Glossary";
|
||||
@@ -54,6 +55,13 @@ function SearchPage() {
|
||||
const [furigana, set_furigana] = useState("");
|
||||
const [isAutoKana, set_isAutoKana] = useState(true);
|
||||
const [isScrollEnd, set_isScrollEnd] = useState(false);
|
||||
const [yomitanURL, set_yomitanURL] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
set_yomitanURL(convertFileSrc(await join(await appDataDir(), "yomitan")));
|
||||
})();
|
||||
});
|
||||
|
||||
const nSearch = useRef(0);
|
||||
const searchboxRef = useRef<HTMLInputElement | null>(null);
|
||||
@@ -69,6 +77,9 @@ function SearchPage() {
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
const newText = (await readText()).replace(/ /g, "");
|
||||
if (!/[\p{sc=Han}\p{scx=Hiragana}\p{scx=Katakana}〆]/u.test(newText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_clipboardText((clipboardText) => {
|
||||
if (clipboardText !== newText) {
|
||||
@@ -411,7 +422,7 @@ function SearchPage() {
|
||||
<Glossary
|
||||
key={i}
|
||||
glossary={g}
|
||||
onTermClicked={onTermClicked}
|
||||
{...{ yomitanURL, onTermClicked }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["protocol-asset"] }
|
||||
tauri-plugin-opener = "2"
|
||||
cjdic2-core = { path = "../src-core" }
|
||||
tauri-plugin-http = "2"
|
||||
|
||||
+22
-25
@@ -34,38 +34,36 @@ pub async fn init_yomitan(
|
||||
lang: String,
|
||||
state: tauri::State<'_, AppService>,
|
||||
) -> Result<(), CJDicError> {
|
||||
let app_dir = app
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| CJDicError::Error(e.to_string()))?;
|
||||
|
||||
let yomitan_assets = data_dir.join("yomitan");
|
||||
|
||||
for d in dicts.iter().clone() {
|
||||
let zip_path = app_dir.join(&d.filepath);
|
||||
let zip_path = data_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();
|
||||
},
|
||||
)
|
||||
download_url_local(&d.url, &data_dir.clone(), &d.filepath, |progress| {
|
||||
app.emit("download-url-progress", progress).unwrap();
|
||||
})
|
||||
.await
|
||||
.map_err(|e| CJDicError::Error(e.to_string()))?;
|
||||
}
|
||||
|
||||
let zip_files: Vec<PathBuf> = dicts
|
||||
.into_iter()
|
||||
.map(|d| app_dir.join(&d.filepath))
|
||||
.map(|d| data_dir.join(&d.filepath))
|
||||
.collect();
|
||||
|
||||
let update_count = Cell::new(0usize);
|
||||
|
||||
state.load_yomitan_zip_dir(
|
||||
zip_files,
|
||||
&yomitan_assets,
|
||||
lang.as_str(),
|
||||
|r| {
|
||||
update_count.set(update_count.get() + r.new_dicts.len() + r.to_be_removed_dicts.len());
|
||||
@@ -97,6 +95,9 @@ pub async fn import_yomitan_dict(
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| CJDicError::Error(e.to_string()))?;
|
||||
|
||||
let yomitan_assets = data_dir.join("yomitan");
|
||||
|
||||
data_dir.push("yomitan");
|
||||
data_dir.push(lang);
|
||||
data_dir.push(bundle_name);
|
||||
@@ -104,7 +105,7 @@ pub async fn import_yomitan_dict(
|
||||
let mut writer = state.get_yomitan_writer(|r| {
|
||||
app.emit("yomitan-import-progress", r).unwrap();
|
||||
})?;
|
||||
AppService::import_yomitan_zip_file(&mut writer, &data_dir, lang, |r| {
|
||||
AppService::import_yomitan_zip_file(&mut writer, &data_dir, &yomitan_assets, lang, |r| {
|
||||
app.emit("yomitan-import-progress", r).unwrap();
|
||||
})
|
||||
}
|
||||
@@ -151,16 +152,16 @@ struct DownloadProgress {
|
||||
}
|
||||
|
||||
async fn download_url_local<Callback>(
|
||||
url: String,
|
||||
data_dir: PathBuf,
|
||||
filepath: String,
|
||||
url: &str,
|
||||
data_dir: &PathBuf,
|
||||
filepath: &str,
|
||||
callback: Callback,
|
||||
) -> Result<bool, CJDicError>
|
||||
where
|
||||
Callback: Fn(DownloadProgress),
|
||||
{
|
||||
let client = Client::new();
|
||||
let file_pf = data_dir.join(filepath.as_str());
|
||||
let file_pf = data_dir.join(filepath);
|
||||
|
||||
let temp_pf = data_dir.join(format!("{}.dl-tmp", filepath));
|
||||
let temp_path = temp_pf.as_path();
|
||||
@@ -170,7 +171,7 @@ where
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut request = client.get(url.as_str());
|
||||
let mut request = client.get(url);
|
||||
// Request only the remaining bytes
|
||||
if existing_size > 0 {
|
||||
request = request.header("Range", format!("bytes={}-", existing_size));
|
||||
@@ -208,8 +209,8 @@ where
|
||||
downloaded += chunk.len() as u64;
|
||||
|
||||
callback(DownloadProgress {
|
||||
url: url.clone(),
|
||||
filepath: filepath.clone(),
|
||||
url: url.to_owned(),
|
||||
filepath: filepath.to_owned(),
|
||||
content_length,
|
||||
downloaded,
|
||||
});
|
||||
@@ -235,14 +236,10 @@ where
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_url(
|
||||
app: AppHandle,
|
||||
url: String,
|
||||
filepath: String,
|
||||
) -> Result<bool, CJDicError> {
|
||||
pub async fn download_url(app: AppHandle, url: &str, filepath: &str) -> Result<bool, CJDicError> {
|
||||
download_url_local(
|
||||
url,
|
||||
app.path()
|
||||
&app.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| CJDicError::Error(e.to_string()))?,
|
||||
filepath,
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["$APPDATA/yomitan/**"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
||||
Reference in new issue
Block a user