enum-map and more db attach

This commit is contained in:
polv committed 2026-03-08 14:32:32 +07:00
1 parent a38a28a836
commit ec14023c04
6 files changed
+76 -47

No files matched your search

Generated
+21
View File
@@ -639,6 +639,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
"enum-map",
"rusqlite", "rusqlite",
"serde", "serde",
"serde_json", "serde_json",
@@ -1124,6 +1125,26 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
[[package]]
name = "enum-map"
version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
dependencies = [
"enum-map-derive",
]
[[package]]
name = "enum-map-derive"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]] [[package]]
name = "enumflags2" name = "enumflags2"
version = "0.7.12" version = "0.7.12"
+1
View File
@@ -16,3 +16,4 @@ blake3 = "1.8.3"
anyhow = "1.0.102" anyhow = "1.0.102"
vibrato = "0.5.2" vibrato = "0.5.2"
zstd = "0.13.3" zstd = "0.13.3"
enum-map = "2.7.3"
+43 -40
View File
@@ -2,12 +2,13 @@ use std::{
env::current_dir, env::current_dir,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
Arc, Mutex, Arc, LazyLock, Mutex,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
}, },
}; };
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use enum_map::EnumMap;
use rusqlite::Connection; use rusqlite::Connection;
mod yomitan; mod yomitan;
@@ -20,18 +21,32 @@ pub use yomitan_writer::{YomitanProgress, YomitanWriter, YomitanZipImportResult,
pub(crate) struct Database { pub(crate) struct Database {
pub(crate) conn: Arc<Mutex<Connection>>, pub(crate) conn: Arc<Mutex<Connection>>,
pub(crate) dir: PathBuf, pub(crate) dir: PathBuf,
yomitan_attached: Arc<AtomicBool>, is_db_attached: EnumMap<DbChild, Arc<AtomicBool>>,
yomitan_glossary_attached: Arc<AtomicBool>,
} }
enum DbChild { #[derive(Debug, Enum, Clone, Copy)]
pub(crate) enum DbChild {
Yomitan, Yomitan,
YomitanGlossary, YomitanGlossary,
Reading,
} }
const USER_DBFILE: &str = "user.db"; const USER_DBFILE: &str = "user.db";
pub(crate) const YOMITAN_DBFILE: &str = "yomitan.db";
pub(crate) const YOMITAN_GLOSSARY_DBFILE: &str = "yomitan-glossary.db"; pub(crate) const DBFILE: LazyLock<EnumMap<DbChild, &str>> = LazyLock::new(|| {
enum_map! {
DbChild::Yomitan => "yomitan.db",
DbChild::YomitanGlossary => "yomitan-glossary.db",
DbChild::Reading => "reading.db"
}
});
pub(crate) const DBSCHEMA: LazyLock<EnumMap<DbChild, &str>> = LazyLock::new(|| {
enum_map! {
DbChild::Yomitan => "yomitan",
DbChild::YomitanGlossary => "glossary",
DbChild::Reading => "reading"
}
});
impl Database { impl Database {
pub fn new(db_dir: impl AsRef<Path>) -> Result<Self> { pub fn new(db_dir: impl AsRef<Path>) -> Result<Self> {
@@ -50,37 +65,28 @@ impl Database {
Ok(Self { Ok(Self {
conn: Arc::new(Mutex::new(conn)), conn: Arc::new(Mutex::new(conn)),
dir, dir,
yomitan_attached: Arc::new(AtomicBool::new(false)), is_db_attached: enum_map! {
yomitan_glossary_attached: Arc::new(AtomicBool::new(false)), DbChild::Yomitan => Arc::new(AtomicBool::new(false)),
DbChild::YomitanGlossary => Arc::new(AtomicBool::new(false)),
DbChild::Reading => Arc::new(AtomicBool::new(false)),
},
}) })
} }
pub fn yomitan(&self) -> Result<yomitan::YomitanDatabase> { pub fn yomitan(&self) -> Result<yomitan::YomitanDatabase> {
self.ensure_db_attached(DbChild::Yomitan)?; self.ensure_db_attached(DbChild::Yomitan)?;
self.ensure_db_attached(DbChild::YomitanGlossary)?; self.ensure_db_attached(DbChild::YomitanGlossary)?;
self.ensure_db_attached(DbChild::Reading)?;
Ok(yomitan::YomitanDatabase::new(self.clone())) Ok(yomitan::YomitanDatabase::new(self.clone()))
} }
fn ensure_db_attached(&self, db_child: DbChild) -> Result<()> { fn ensure_db_attached(&self, db_child: DbChild) -> Result<()> {
match db_child { if self.is_db_attached[db_child].load(Ordering::Acquire) {
DbChild::Yomitan => {
if self.yomitan_attached.load(Ordering::Acquire) {
return Ok(()); return Ok(());
} }
}
DbChild::YomitanGlossary => {
if self.yomitan_glossary_attached.load(Ordering::Acquire) {
return Ok(());
}
}
}
let conn = self.conn.lock().unwrap(); let conn = self.conn.lock().unwrap();
let is_attached = self.is_db_attached[db_child].load(Ordering::Relaxed);
let is_attached = match db_child {
DbChild::Yomitan => self.yomitan_attached.load(Ordering::Relaxed),
DbChild::YomitanGlossary => self.yomitan_glossary_attached.load(Ordering::Relaxed),
};
if !is_attached { if !is_attached {
let dir = self.dir.clone(); let dir = self.dir.clone();
@@ -90,16 +96,15 @@ impl Database {
current_dir()?.join(dir) current_dir()?.join(dir)
}; };
let db_file = match db_child { let mode = match db_child {
DbChild::Yomitan => YOMITAN_DBFILE, DbChild::Yomitan | DbChild::YomitanGlossary => "?mode=ro&immutable=1",
DbChild::YomitanGlossary => YOMITAN_GLOSSARY_DBFILE, _ => "",
};
let schema_name = match db_child {
DbChild::Yomitan => "yomitan",
DbChild::YomitanGlossary => "glossary",
}; };
let path = path.join(db_file).to_string_lossy().replace(r"\", r"/"); let path = path
.join(DBFILE[db_child])
.to_string_lossy()
.replace(r"\", r"/");
#[cfg(windows)] #[cfg(windows)]
let path = if !path.starts_with("/") { let path = if !path.starts_with("/") {
@@ -108,22 +113,20 @@ impl Database {
path.to_string() path.to_string()
}; };
let uri = format!("file:{}?mode=ro&immutable=1", path); let uri = format!("file:{}{}", path, mode);
conn.execute(&format!("ATTACH DATABASE ?1 AS {}", schema_name), [uri])?; conn.execute(
&format!("ATTACH DATABASE ?1 AS {}", DBSCHEMA[db_child]),
[uri],
)?;
conn.execute_batch(&format!( conn.execute_batch(&format!(
r" r"
PRAGMA {}.mmap_size = 3000000000; -- 3_000_000_000 PRAGMA {}.mmap_size = 3000000000; -- 3_000_000_000
", ",
schema_name DBSCHEMA[db_child]
))?; ))?;
match db_child { self.is_db_attached[db_child].store(true, Ordering::Release);
DbChild::Yomitan => self.yomitan_attached.store(true, Ordering::Release),
DbChild::YomitanGlossary => self
.yomitan_glossary_attached
.store(true, Ordering::Release),
};
} }
Ok(()) Ok(())
+6 -5
View File
@@ -13,7 +13,7 @@ use zip::ZipArchive;
use crate::{ use crate::{
CJDicError, Timer, CJDicError, Timer,
db::{YOMITAN_DBFILE, YOMITAN_GLOSSARY_DBFILE}, db::{DBFILE, DbChild},
}; };
fn blake3_hex(s: &str) -> String { fn blake3_hex(s: &str) -> String {
@@ -65,7 +65,7 @@ pub struct YomitanWriter {
impl YomitanWriter { impl YomitanWriter {
pub fn new(db_dir: impl AsRef<Path>) -> Result<Self> { pub fn new(db_dir: impl AsRef<Path>) -> Result<Self> {
let dir = db_dir.as_ref().to_path_buf(); let dir = db_dir.as_ref().to_path_buf();
let conn = Connection::open(dir.join(YOMITAN_DBFILE))?; let conn = Connection::open(dir.join(DBFILE[DbChild::Yomitan]))?;
conn.execute_batch( conn.execute_batch(
" "
@@ -193,7 +193,7 @@ impl YomitanWriter {
.query_row("SELECT MAX(id) FROM glossaries", [], |r| r.get::<_, i64>(0))? .query_row("SELECT MAX(id) FROM glossaries", [], |r| r.get::<_, i64>(0))?
as usize; as usize;
let message = &format!("Migrate to {}", YOMITAN_GLOSSARY_DBFILE); let message = &format!("Migrate to {}", DBFILE[DbChild::YomitanGlossary]);
progress_callback(YomitanProgress { progress_callback(YomitanProgress {
message: message.to_string(), message: message.to_string(),
current: 0, current: 0,
@@ -255,7 +255,8 @@ impl YomitanWriter {
steps: total, steps: total,
}); });
let _timer = Timer::new(format!("cleanup for {}", YOMITAN_GLOSSARY_DBFILE)); let _timer =
Timer::new(format!("cleanup for {}", DBFILE[DbChild::YomitanGlossary]));
self.conn.execute_batch( self.conn.execute_batch(
" "
UPDATE glossaries SET content = '[]'; UPDATE glossaries SET content = '[]';
@@ -368,7 +369,7 @@ impl YomitanWriter {
current_dir()?.join(dir) current_dir()?.join(dir)
}; };
let db_file = YOMITAN_GLOSSARY_DBFILE; let db_file = DBFILE[DbChild::YomitanGlossary];
let schema_name = "glossary"; let schema_name = "glossary";
let path = path.join(db_file).to_string_lossy().replace(r"\", r"/"); let path = path.join(db_file).to_string_lossy().replace(r"\", r"/");
+3
View File
@@ -1,3 +1,6 @@
#[macro_use]
extern crate enum_map;
mod db; mod db;
mod error; mod error;
mod service; mod service;
+2 -2
View File
@@ -11,7 +11,7 @@ use vibrato::{Dictionary, Tokenizer};
use crate::{ use crate::{
Timer, ZipSource, Timer, ZipSource,
db::{ db::{
Database, YOMITAN_DBFILE, YomitanProgress, YomitanRow, YomitanWriter, DBFILE, Database, DbChild, YomitanProgress, YomitanRow, YomitanWriter,
YomitanZipImportResult, YomitanZipImportResult,
}, },
error::CJDicError, error::CJDicError,
@@ -182,7 +182,7 @@ impl AppService {
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![];
let db_path = self.db.dir.join(YOMITAN_DBFILE); let db_path = self.db.dir.join(DBFILE[DbChild::Yomitan]);
if db_path.exists() { if db_path.exists() {
let conn = Connection::open(&db_path)?; let conn = Connection::open(&db_path)?;