add delete dict operations

This commit is contained in:
polv committed 2026-02-23 17:27:41 +07:00
1 parent fa5bfb56b0
commit 1e91f21ca4
8 files changed
+108 -23

No files matched your search

@@ -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);
}
@@ -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(())
File renamed without changes.
+73 -19
View File
@@ -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<Self> {
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
@@ -109,8 +112,15 @@ impl YomitanWriter {
"
CREATE INDEX IF NOT EXISTS dictionaries_lang ON dictionaries (lang);
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);
",
)?;
@@ -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<YomitanZipImportResult, CJDicError> {
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
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();
.to_string(),
);
let revision = index_file
.get("revision")
.and_then(Value::as_str)
.unwrap_or("")
.to_string();
{
let title: &str = &title;
// Skip if already installed
let exists: bool = self
let mut stmt = self
.conn
.prepare("SELECT 1 FROM dictionaries WHERE title = ?1 AND revision = ?2")?
.exists([title, revision])?;
if exists {
.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(
+9 -2
View File
@@ -43,7 +43,7 @@ impl AppService {
pub fn get_yomitan_writer(&self) -> Result<YomitanWriter, CJDicError> {
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<YomitanZipImportResult, CJDicError> {
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)?)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

+15
View File
@@ -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;
}
+8
View File
@@ -68,12 +68,20 @@ function App() {
<Card.Root>
<Card.Header>{JSON.stringify(it)}</Card.Header>
<Card.Body>
<div
style={{
gap: "1em",
display: "flex",
flexDirection: "column",
}}
>
{JSON.parse(glossary_json).map((g: any) => (
<Glossary
glossary={g}
onTermClicked={(t) => setQ(t + " ")}
/>
))}
</div>
</Card.Body>
</Card.Root>
</details>