1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fs::File;
4use std::io::BufReader;
5use std::path::PathBuf;
6use encoding_rs::{Encoding, UTF_16LE};
7use crate::parser::{decode_slice_string, load, lookup_record};
8use crate::{Error, Result};
9
10pub type Reader = BufReader<File>;
11
12pub trait KeyMaker {
13 fn make(&self, key: &Cow<str>, resource: bool) -> String;
14}
15
16impl<F> KeyMaker for F where F: Fn(&Cow<str>, bool) -> String {
17 #[inline]
18 fn make(&self, key: &Cow<str>, resource: bool) -> String
19 {
20 self(key, resource)
21 }
22}
23
24pub struct MDict<M: KeyMaker> {
25 pub(crate) mdx: Mdx,
26 pub(crate) resources: Vec<Mdx>,
27 pub(crate) key_maker: M,
28}
29
30pub struct Mdx {
31 pub(crate) encoding: &'static Encoding,
32 pub(crate) title: String,
33 #[allow(unused)]
34 pub(crate) encrypted: u8,
35 pub(crate) key_entries: Vec<KeyEntry>,
36 pub(crate) records_info: Vec<BlockEntryInfo>,
37 pub(crate) reader: Reader,
38 pub(crate) record_block_offset: u64,
39 pub(crate) record_cache: Option<HashMap<usize, Vec<u8>>>,
40}
41
42#[derive(Debug)]
43pub(crate) struct KeyEntry {
44 pub(crate) offset: usize,
45 pub(crate) text: String,
46}
47
48#[derive(Debug)]
49pub(crate) struct BlockEntryInfo {
50 pub(crate) compressed_size: usize,
51 pub(crate) decompressed_size: usize,
52}
53
54#[derive(Debug)]
55pub(crate) struct RecordOffset {
56 pub(crate) buf_offset: usize,
57 pub(crate) block_offset: usize,
58 pub(crate) record_size: usize,
59 pub(crate) decomp_size: usize,
60}
61
62#[derive(Debug)]
63pub struct WordDefinition<'a> {
64 pub key: &'a str,
65 pub definition: String,
66}
67
68impl<M: KeyMaker> MDict<M> {
69 pub fn lookup<'a>(&mut self, word: &'a str) -> Result<Option<WordDefinition<'a>>>
70 {
71 let encoding = self.mdx.encoding;
72 let key = self.key_maker.make(&Cow::Borrowed(word), false);
73 if let Some(slice) = lookup_record(&mut self.mdx, &key)? {
74 let definition = decode_slice_string(&slice, encoding)?.0.to_string();
75 Ok(Some(WordDefinition { key: word, definition }))
76 } else {
77 Ok(None)
78 }
79 }
80
81 pub fn get_resource(&mut self, path: &str) -> Result<Option<Cow<[u8]>>>
82 {
83 let key = self.key_maker.make(&Cow::Borrowed(path), true);
84 for mdx in &mut self.resources {
85 if let Some(slice) = lookup_record(mdx, &key)? {
86 return Ok(Some(slice));
87 }
88 }
89 Ok(None)
90 }
91
92 pub fn title(&self) -> &str
93 {
94 &self.mdx.title
95 }
96}
97
98pub struct MDictBuilder {
99 path: PathBuf,
100 cache_definition: bool,
101 cache_resource: bool,
102}
103
104impl MDictBuilder {
105 pub fn new(path: impl Into<PathBuf>) -> Self
106 {
107 MDictBuilder {
108 path: path.into(),
109 cache_definition: false,
110 cache_resource: false,
111 }
112 }
113
114 #[inline]
115 pub fn cache_definition(mut self, cache: bool) -> Self
116 {
117 self.cache_definition = cache;
118 self
119 }
120 #[inline]
121 pub fn cache_resource(mut self, cache: bool) -> Self
122 {
123 self.cache_resource = cache;
124 self
125 }
126 #[inline]
127 pub fn build(self) -> Result<MDict<impl KeyMaker>>
128 {
129 self.build_with_key_maker(|key: &Cow<str>, _resource: bool| key.to_ascii_lowercase())
130 }
131 pub fn build_with_key_maker<M: KeyMaker>(self, key_maker: M)
132 -> Result<MDict<M>>
133 {
134 let path = self.path;
135 let f = File::open(&path)?;
136 let reader = BufReader::new(f);
137 let cwd = path.parent()
138 .ok_or_else(|| Error::InvalidPath(path.clone()))?
139 .canonicalize()?;
140 let mdx = load(
141 reader,
142 UTF_16LE,
143 self.cache_definition,
144 &key_maker,
145 false)?;
146 let filename = path.file_stem()
147 .ok_or_else(|| Error::InvalidPath(path.clone()))?
148 .to_str()
149 .ok_or_else(|| Error::InvalidPath(path.clone()))?;
150 let resources = load_resources(
151 &cwd,
152 filename,
153 self.cache_resource,
154 &key_maker)?;
155 Ok(MDict {
156 mdx,
157 resources,
158 key_maker,
159 })
160 }
161}
162
163fn load_resources(cwd: &PathBuf, name: &str, cache_resources: bool,
164 key_maker: &dyn KeyMaker) -> Result<Vec<Mdx>>
165{
166 let mut resources = vec![];
167 let path = cwd.join(format!("{}.mdd", name));
169 if !path.exists() {
170 return Ok(resources);
171 }
172 let f = File::open(&path)?;
173 let reader = BufReader::new(f);
174 resources.push(load(
175 reader,
176 UTF_16LE,
177 cache_resources,
178 key_maker,
179 true)?);
180
181 let mut i = 1;
183 loop {
184 let path = cwd.join(format!("{}.{}.mdd", name, i));
185 if !path.exists() {
186 break;
187 }
188 let f = File::open(&path)?;
189 let reader = BufReader::new(f);
190 resources.push(load(
191 reader,
192 UTF_16LE,
193 cache_resources,
194 key_maker,
195 true)?);
196 i += 1;
197 }
198 Ok(resources)
199}