Skip to main content

dictx_index/
pack.rs

1use dictx_core::{DictEntry, DictxError, Result};
2use std::fs::File;
3use std::io::{Read, Seek, SeekFrom, Write};
4use std::path::Path;
5use std::sync::Mutex;
6
7pub const ENTRY_PACK_FILE: &str = "entries.dxp";
8const MAGIC: &[u8; 8] = b"DXPCK001";
9
10#[derive(Debug, Clone, Copy)]
11pub struct EntryLocator {
12    pub offset: u64,
13    pub len: u64,
14}
15
16pub struct EntryPackWriter {
17    file: File,
18}
19
20impl EntryPackWriter {
21    pub fn create(path: &Path) -> Result<Self> {
22        let mut file = File::create(path)?;
23        file.write_all(MAGIC)?;
24        Ok(Self { file })
25    }
26
27    pub fn append(&mut self, entry: &DictEntry) -> Result<EntryLocator> {
28        let bytes = rmp_serde::to_vec_named(entry)
29            .map_err(|err| DictxError::InvalidData(format!("词条二进制序列化失败: {err}")))?;
30        let offset = self.file.stream_position()?;
31        self.file.write_all(&(bytes.len() as u32).to_le_bytes())?;
32        self.file.write_all(&bytes)?;
33        Ok(EntryLocator {
34            offset,
35            len: bytes.len() as u64,
36        })
37    }
38
39    pub fn finish(mut self) -> Result<()> {
40        self.file.flush()?;
41        Ok(())
42    }
43}
44
45pub struct EntryPackReader {
46    file: Mutex<File>,
47}
48
49impl EntryPackReader {
50    pub fn open(path: &Path) -> Result<Self> {
51        let mut file = File::open(path)?;
52        let mut magic = [0u8; 8];
53        file.read_exact(&mut magic)?;
54        if &magic != MAGIC {
55            return Err(DictxError::InvalidData(format!(
56                "词条 pack 文件格式不正确: {}",
57                path.display()
58            )));
59        }
60        Ok(Self {
61            file: Mutex::new(file),
62        })
63    }
64
65    pub fn read(&self, locator: EntryLocator) -> Result<DictEntry> {
66        let mut file = self
67            .file
68            .lock()
69            .map_err(|_| DictxError::Message("词条 pack 读取锁已损坏".to_string()))?;
70        file.seek(SeekFrom::Start(locator.offset))?;
71
72        let mut len_buf = [0u8; 4];
73        file.read_exact(&mut len_buf)?;
74        let len = u32::from_le_bytes(len_buf) as u64;
75        if len != locator.len {
76            return Err(DictxError::InvalidData(format!(
77                "词条 pack 长度不一致: index={} pack={}",
78                locator.len, len
79            )));
80        }
81
82        let mut bytes = vec![0u8; len as usize];
83        file.read_exact(&mut bytes)?;
84        rmp_serde::from_slice(&bytes)
85            .map_err(|err| DictxError::InvalidData(format!("词条二进制反序列化失败: {err}")))
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use dictx_core::{Definition, DictSource};
93
94    #[test]
95    fn roundtrips_entry_pack() {
96        let dir = tempfile::tempdir().unwrap();
97        let path = dir.path().join(ENTRY_PACK_FILE);
98        let mut entry = DictEntry::new(DictSource::Custom { name: "t".into() }, "apple");
99        entry
100            .definitions
101            .push(Definition::new("fruit", "苹果", Some("n".into())));
102
103        let mut writer = EntryPackWriter::create(&path).unwrap();
104        let locator = writer.append(&entry).unwrap();
105        writer.finish().unwrap();
106
107        let reader = EntryPackReader::open(&path).unwrap();
108        let decoded = reader.read(locator).unwrap();
109        assert_eq!(decoded.word, "apple");
110        assert_eq!(decoded.definitions[0].zh, "苹果");
111    }
112}