From 1e91f21ca4a7aecfbf8a450f48a10f52e7ad9ecf Mon Sep 17 00:00:00 2001 From: polv Date: Mon, 23 Feb 2026 17:27:41 +0700 Subject: [PATCH] add delete dict operations --- ...-yomitan.rs => add_yomitan_dict_folder.rs} | 2 +- ...d-schema.rs => remove_one_yomitan_dict.rs} | 3 +- .../{search-yomitan.rs => search_yomitan.rs} | 0 src-core/src/db/yomitan_writer.rs | 122 +++++++++++++----- src-core/src/service.rs | 11 +- src-react/public/assets/pixiv-logo.png | Bin 0 -> 254 bytes src-react/src/App.css | 15 +++ src-react/src/App.tsx | 20 ++- 8 files changed, 129 insertions(+), 44 deletions(-) rename src-core/examples/{add-to-yomitan.rs => add_yomitan_dict_folder.rs} (95%) rename src-core/examples/{yomitan-mod-schema.rs => remove_one_yomitan_dict.rs} (70%) rename src-core/examples/{search-yomitan.rs => search_yomitan.rs} (100%) create mode 100644 src-react/public/assets/pixiv-logo.png diff --git a/src-core/examples/add-to-yomitan.rs b/src-core/examples/add_yomitan_dict_folder.rs similarity index 95% rename from src-core/examples/add-to-yomitan.rs rename to src-core/examples/add_yomitan_dict_folder.rs index aed65f7..f4d1b34 100644 --- a/src-core/examples/add-to-yomitan.rs +++ b/src-core/examples/add_yomitan_dict_folder.rs @@ -31,7 +31,7 @@ fn main() -> Result<(), anyhow::Error> { "{:?}", AppService::import_yomitan_zip_file(&mut writer, p, "ja")?, ); - println!("[{:.2?}]", start.elapsed(),); + println!("[{:.2?}]", start.elapsed()); } else { println!("not zip_file: {:?}", p); } diff --git a/src-core/examples/yomitan-mod-schema.rs b/src-core/examples/remove_one_yomitan_dict.rs similarity index 70% rename from src-core/examples/yomitan-mod-schema.rs rename to src-core/examples/remove_one_yomitan_dict.rs index 0a04ff7..cd861e9 100644 --- a/src-core/examples/yomitan-mod-schema.rs +++ b/src-core/examples/remove_one_yomitan_dict.rs @@ -10,7 +10,8 @@ fn main() -> Result<(), anyhow::Error> { let service = AppService::new(get_db_dir())?; let start = Instant::now(); - service.get_yomitan_writer()?; + let mut writer = service.get_yomitan_writer()?; + AppService::remove_yomitan_dictionary(&mut writer, "JMnedict")?; println!("[{:.2?}]", start.elapsed()); Ok(()) diff --git a/src-core/examples/search-yomitan.rs b/src-core/examples/search_yomitan.rs similarity index 100% rename from src-core/examples/search-yomitan.rs rename to src-core/examples/search_yomitan.rs diff --git a/src-core/src/db/yomitan_writer.rs b/src-core/src/db/yomitan_writer.rs index 4f58687..cd3c66f 100644 --- a/src-core/src/db/yomitan_writer.rs +++ b/src-core/src/db/yomitan_writer.rs @@ -1,10 +1,9 @@ -use std::{collections::HashMap, fs::File, io::Read, path::PathBuf}; - use anyhow::Context; use blake3::hash; use rusqlite::{Connection, Result, Transaction, params}; use serde::Serialize; use serde_json::Value; +use std::{collections::HashMap, fs::File, io::Read, path::PathBuf, time::Instant}; use zip::ZipArchive; use crate::CJDicError; @@ -25,19 +24,23 @@ pub struct YomitanWriter { } impl YomitanWriter { - pub fn new(conn: Connection) -> Self { - Self { conn } - } - - pub fn create_schema(&self) -> Result<()> { - self.conn.execute_batch( - " + pub fn new(conn: Connection) -> Result { + conn.execute_batch( + " PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA synchronous = NORMAL; PRAGMA cache_size = -65536; PRAGMA temp_store = MEMORY; + ", + )?; + Ok(Self { conn }) + } + + pub fn create_schema(&self) -> Result<()> { + self.conn.execute_batch( + " CREATE TABLE IF NOT EXISTS schema_meta ( key TEXT PRIMARY KEY, value TEXT NOT NULL @@ -107,11 +110,18 @@ impl YomitanWriter { self.conn.execute_batch( " - CREATE INDEX IF NOT EXISTS dictionaries_lang ON dictionaries (lang); + CREATE INDEX IF NOT EXISTS dictionaries_lang ON dictionaries (lang); - CREATE INDEX IF NOT EXISTS terms_term ON terms (term); - CREATE INDEX IF NOT EXISTS terms_reading ON terms (reading); - ", + CREATE INDEX IF NOT EXISTS terms_dict_id ON terms (dict_id); + CREATE INDEX IF NOT EXISTS terms_term ON terms (term); + CREATE INDEX IF NOT EXISTS terms_reading ON terms (reading); + + CREATE INDEX IF NOT EXISTS term_meta_dict_id ON term_meta (dict_id); + + CREATE INDEX IF NOT EXISTS tags_dict_id ON tags (dict_id); + + CREATE INDEX IF NOT EXISTS kanji_dict_id ON kanji (dict_id); + ", )?; let mut stmt = self @@ -131,12 +141,15 @@ impl YomitanWriter { Ok(()) } - pub fn import_bundled_zip_file( + pub fn import_dictionary_zip_file( &mut self, zip_file: PathBuf, lang: &str, ) -> anyhow::Result { - self.create_schema()?; + self.create_schema()?; // < 1 ms + + let start_time = Instant::now(); + self.conn.execute_batch("PRAGMA foreign_keys = off")?; let f = File::open(&zip_file).with_context(|| format!("opening zip {}", zip_file.display()))?; @@ -159,36 +172,57 @@ impl YomitanWriter { } }; - let title = index_file - .get("title") - .and_then(Value::as_str) - .unwrap_or("") - .to_string(); + fn remove_timestamp(s: &str) -> String { + if let Some(pos) = s.rfind('[') { + // Trim whitespace only before the '[' and return the substring + let trimmed_before_bracket = s[..pos].trim_end(); + trimmed_before_bracket.to_string() + } else { + s.to_string() // Return the original string if no '[' is found + } + } + + let title = remove_timestamp( + &index_file + .get("title") + .and_then(Value::as_str) + .unwrap_or("") + .to_string(), + ); + let revision = index_file .get("revision") .and_then(Value::as_str) .unwrap_or("") .to_string(); - // Skip if already installed - let exists: bool = self - .conn - .prepare("SELECT 1 FROM dictionaries WHERE title = ?1 AND revision = ?2")? - .exists([title, revision])?; - if exists { - return Ok(YomitanZipImportResult { - exists: true, - load: false, - error: None, - }); + { + let title: &str = &title; + // Skip if already installed + let mut stmt = self + .conn + .prepare("SELECT (revision < ?2) FROM dictionaries WHERE title = ?1")?; + let mut rows = stmt.query(params![title, revision])?; + if let Some(row) = rows.next()? { + let is_outdated: bool = row.get(0)?; + if is_outdated { + self.drop_dictionary(title)?; + } else { + return Ok(YomitanZipImportResult { + exists: true, + load: false, + error: None, + }); + } + } } let tx = self.conn.transaction()?; tx.execute( "INSERT INTO dictionaries (title, revision, author, url, description, lang) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![ - index_file.get("title").and_then(Value::as_str), - index_file.get("revision").and_then(Value::as_str), + title, + revision, index_file.get("author").and_then(Value::as_str), index_file.get("url").and_then(Value::as_str), index_file.get("description").and_then(Value::as_str), @@ -377,7 +411,20 @@ impl YomitanWriter { tx.commit()?; - self.conn.execute_batch("VACUUM;")?; + println!( + "Importing {:?} takes {:.2?}", + zip_file, + start_time.elapsed() + ); + + self.conn.execute_batch( + " + PRAGMA foreign_keys = on; + PRAGMA check_foreign_keys; + ", + )?; // < 1 ms + + // self.conn.execute_batch("VACUUM;")?; // Not needed, twice time taken than import Ok(YomitanZipImportResult { exists: true, @@ -385,6 +432,13 @@ impl YomitanWriter { error: None, }) } + + pub fn drop_dictionary(&self, title: &str) -> Result<()> { + self.conn + .execute("DELETE FROM dictionaries WHERE title = ?1", [title])?; + + Ok(()) + } } fn intern( diff --git a/src-core/src/service.rs b/src-core/src/service.rs index 6e94caa..cd13864 100644 --- a/src-core/src/service.rs +++ b/src-core/src/service.rs @@ -43,7 +43,7 @@ impl AppService { pub fn get_yomitan_writer(&self) -> Result { let conn = Connection::open(self.db.dir.join("yomitan.db"))?; - let writer = YomitanWriter::new(conn); + let writer = YomitanWriter::new(conn)?; writer.create_schema()?; Ok(writer) } @@ -53,6 +53,13 @@ impl AppService { zip_file: PathBuf, lang: &str, ) -> Result { - Ok(writer.import_bundled_zip_file(zip_file, lang)?) + Ok(writer.import_dictionary_zip_file(zip_file, lang)?) + } + + pub fn remove_yomitan_dictionary( + writer: &mut YomitanWriter, + title: &str, + ) -> Result<(), CJDicError> { + Ok(writer.drop_dictionary(title)?) } } diff --git a/src-react/public/assets/pixiv-logo.png b/src-react/public/assets/pixiv-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3e9e45776560524bbbf4e048b9a0d35cf7b6e5b1 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!aez;Vt9yaZ|NsB5zx(&$*MEk- zUkp=zF--f#Fd0lv`!)IW-@x_1ci#V3x&M#y{9h^Cf15A+{ab2tD^NRENswPK15h&% ze0!5NGsSf^P_)3)#W6%;YHEKZ-(dp|mq<2|e9w3Fw{Asjom_V0yo~^l%gYZ-l?7Gn zH7~xH{dJS2^Wk`Puib(f3>_NA-aNh+VnfAp0vI*KqaT>LKihMV)%oeRMzQl}_BpPw wot74;8DY6<-rrL_QWy8DE&6Z!{r5k1hOex1-@i|D0@}^s>FVdQ&MBb@08ZF$iU0rr literal 0 HcmV?d00001 diff --git a/src-react/src/App.css b/src-react/src/App.css index 4ad5a45..eae11d5 100644 --- a/src-react/src/App.css +++ b/src-react/src/App.css @@ -1,9 +1,24 @@ +a { + text-decoration: underline; + color: teal; +} + +span { + display: inline-flex; + gap: 0.5em; + align-items: center; +} + @media (prefers-color-scheme: dark) { :root { color: #f6f6f6; background-color: #2f2f2f; } + a { + color: lightgray; + } + a:hover { color: #24c8db; } diff --git a/src-react/src/App.tsx b/src-react/src/App.tsx index 5bde710..7fae92b 100644 --- a/src-react/src/App.tsx +++ b/src-react/src/App.tsx @@ -68,12 +68,20 @@ function App() { {JSON.stringify(it)} - {JSON.parse(glossary_json).map((g: any) => ( - setQ(t + " ")} - /> - ))} +
+ {JSON.parse(glossary_json).map((g: any) => ( + setQ(t + " ")} + /> + ))} +