1use std::{
2 collections::{btree_map::Entry, BTreeMap},
3 fs::{read_dir, read_to_string},
4 path::PathBuf,
5 sync::Arc,
6};
7
8#[cfg(debug_assertions)]
9use std::time::SystemTime;
10
11use crate::fnv1a_64;
12use tiny_web_macro::fnv1a_64 as m_fnv1a_64;
13
14#[cfg(debug_assertions)]
15use tokio::fs;
16
17use toml::{Table, Value};
18
19use super::{data::Data, dbs::adapter::DB, log::Log};
20
21#[derive(Debug, Clone)]
31pub struct LangItem {
32 pub id: i64,
34 pub code: String,
36 pub name: String,
38 pub index: i64,
40}
41
42type LangList = BTreeMap<i64, BTreeMap<i64, BTreeMap<i64, Arc<BTreeMap<i64, String>>>>>;
52
53#[derive(Debug)]
61pub struct Lang {
62 pub langs: Arc<Vec<Arc<LangItem>>>,
64 pub list: Arc<LangList>,
66 pub default: usize,
68 #[cfg(debug_assertions)]
70 last: SystemTime,
71 #[cfg(debug_assertions)]
73 hash: i128,
74 #[cfg(debug_assertions)]
76 pub(crate) root: Arc<String>,
77 codes: BTreeMap<String, i64>,
79}
80
81impl Lang {
82 pub async fn new(root: Arc<String>, default_lang: &str, db: &mut DB) -> Lang {
120 #[cfg(debug_assertions)]
121 let last_time = SystemTime::UNIX_EPOCH;
122 let mut codes = BTreeMap::new();
123
124 let files = Lang::get_files(Arc::clone(&root)).await;
125
126 let langs = if db.in_use() { Lang::get_langs(db).await } else { Lang::get_langs_install(&files) };
127
128 if langs.is_empty() {
129 Log::warning(1151, None);
130 return Lang {
131 langs: Arc::new(Vec::new()),
132 list: Arc::new(BTreeMap::new()),
133 default: 0,
134 #[cfg(debug_assertions)]
135 last: last_time,
136 #[cfg(debug_assertions)]
137 hash: 0,
138 #[cfg(debug_assertions)]
139 root,
140 codes,
141 };
142 }
143
144 let mut default = 0;
145 for item in &langs {
146 codes.insert(item.code.clone(), item.id);
147 if item.code == default_lang {
148 default = item.id as usize;
149 }
150 }
151 let mut lang = Lang {
152 langs: Arc::new(langs),
153 list: Arc::new(BTreeMap::new()),
154 default,
155 #[cfg(debug_assertions)]
156 last: last_time,
157 #[cfg(debug_assertions)]
158 hash: 0,
159 #[cfg(debug_assertions)]
160 root,
161 codes,
162 };
163 lang.load(files).await;
164 lang
165 }
166
167 pub(crate) async fn get_all_langs(db: Arc<DB>) -> Vec<LangItem> {
168 let mut vec = Vec::with_capacity(200);
169 if db.in_use() {
170 let res = match db.query_prepare(m_fnv1a_64!("lib_get_all_langs"), &[], false).await {
171 Some(r) => r,
172 None => {
173 Log::warning(1150, None);
174 return Vec::new();
175 }
176 };
177 if res.is_empty() {
178 Log::warning(1151, None);
179 return Vec::new();
180 }
181 for row in res {
182 if let Data::Vec(row) = row {
183 if row.len() != 4 {
184 Log::warning(1150, None);
185 return Vec::new();
186 }
187 let id = if let Data::I64(val) = unsafe { row.get_unchecked(0) } {
188 *val
189 } else {
190 Log::warning(1150, None);
191 return Vec::new();
192 };
193 let index = if let Data::I64(val) = unsafe { row.get_unchecked(3) } {
194 *val
195 } else {
196 Log::warning(1150, None);
197 return Vec::new();
198 };
199 let code = if let Data::String(val) = unsafe { row.get_unchecked(1) } {
200 val.to_owned()
201 } else {
202 Log::warning(1150, None);
203 return Vec::new();
204 };
205 let name = if let Data::String(val) = unsafe { row.get_unchecked(2) } {
206 val.to_owned()
207 } else {
208 Log::warning(1150, None);
209 return Vec::new();
210 };
211 vec.push(LangItem { id, code, name, index });
212 } else {
213 Log::warning(1150, None);
214 return Vec::new();
215 }
216 }
217 } else {
218 return Lang::gelt_all_langs_install();
219 }
220
221 vec
222 }
223
224 async fn get_langs(db: &mut DB) -> Vec<Arc<LangItem>> {
226 let res = match db.query_prepare(m_fnv1a_64!("lib_get_langs"), &[], false).await {
227 Some(r) => r,
228 None => {
229 Log::warning(1150, None);
230 return Vec::new();
231 }
232 };
233 if res.is_empty() {
234 Log::warning(1151, None);
235 return Vec::new();
236 }
237 let mut vec = Vec::with_capacity(res.len());
238 for row in res {
239 if let Data::Vec(row) = row {
240 if row.len() != 4 {
241 Log::warning(1150, None);
242 return Vec::new();
243 }
244 let id = if let Data::I64(val) = unsafe { row.get_unchecked(0) } {
245 *val
246 } else {
247 Log::warning(1150, None);
248 return Vec::new();
249 };
250 let index = if let Data::I64(val) = unsafe { row.get_unchecked(3) } {
251 *val
252 } else {
253 Log::warning(1150, None);
254 return Vec::new();
255 };
256 let code = if let Data::String(val) = unsafe { row.get_unchecked(1) } {
257 val.to_owned()
258 } else {
259 Log::warning(1150, None);
260 return Vec::new();
261 };
262 let name = if let Data::String(val) = unsafe { row.get_unchecked(2) } {
263 val.to_owned()
264 } else {
265 Log::warning(1150, None);
266 return Vec::new();
267 };
268 vec.push(Arc::new(LangItem { id, code, name, index }));
269 } else {
270 Log::warning(1150, None);
271 return Vec::new();
272 }
273 }
274 vec
275 }
276
277 fn gelt_all_langs_install() -> Vec<LangItem> {
279 let list = vec![
280 LangItem {
281 id: 0,
282 code: "en".to_owned(),
283 name: "English".to_string(),
284 index: m_fnv1a_64!("en"),
285 },
286 LangItem {
287 id: 1,
288 code: "uk".to_owned(),
289 name: "Ukrainian (Українська)".to_string(),
290 index: m_fnv1a_64!("uk"),
291 },
292 LangItem {
293 id: 28,
294 code: "cs".to_owned(),
295 name: "Czech (Čeština)".to_string(),
296 index: m_fnv1a_64!("cs"),
297 },
298 LangItem {
299 id: 40,
300 code: "et".to_owned(),
301 name: "Estonian (Eesti)".to_string(),
302 index: m_fnv1a_64!("et"),
303 },
304 LangItem {
305 id: 97,
306 code: "lt".to_owned(),
307 name: "Lithuanian (Lietuvių kalba)".to_string(),
308 index: m_fnv1a_64!("lt"),
309 },
310 LangItem {
311 id: 99,
312 code: "lv".to_owned(),
313 name: "Latvian (Latviešu valoda)".to_string(),
314 index: m_fnv1a_64!("lv"),
315 },
316 LangItem {
317 id: 117,
318 code: "no".to_owned(),
319 name: "Norwegian (Norsk)".to_string(),
320 index: m_fnv1a_64!("no"),
321 },
322 LangItem {
323 id: 128,
324 code: "pl".to_owned(),
325 name: "Polish (Język polski)".to_string(),
326 index: m_fnv1a_64!("pl"),
327 },
328 ];
329 list
330 }
331
332 pub(crate) fn get_langs_install(files: &Vec<(PathBuf, String, String, String)>) -> Vec<Arc<LangItem>> {
334 let list = Lang::gelt_all_langs_install();
335
336 let mut vec = Vec::with_capacity(files.len());
337 let mut index = 0;
338 for (_, module, class, code) in files {
339 if module == "index" && class == "install" {
340 for lang in &list {
341 if lang.index == fnv1a_64(code.as_bytes()) {
342 let mut l = lang.clone();
343 l.index = index;
344 vec.push(Arc::new(l));
345 index += 1;
346 break;
347 }
348 }
349 }
350 }
351
352 vec
353 }
354
355 pub(crate) async fn get_files(root: Arc<String>) -> Vec<(PathBuf, String, String, String)> {
357 let mut vec = Vec::new();
358
359 let path = format!("{}/app/", root);
360 let read_path = match read_dir(&path) {
361 Ok(r) => r,
362 Err(e) => {
363 Log::warning(1100, Some(format!("Path: {}. Err: {}", path, e)));
364 return vec;
365 }
366 };
367
368 for entry in read_path {
370 let path = match entry {
371 Ok(e) => e.path(),
372 Err(e) => {
373 Log::warning(1101, Some(format!("{} ({})", e, path)));
374 continue;
375 }
376 };
377 if !path.is_dir() {
378 continue;
379 }
380 let module = match path.file_name() {
381 Some(m) => match m.to_str() {
382 Some(module) => module,
383 None => continue,
384 },
385 None => continue,
386 };
387 let read_path = match read_dir(&path) {
388 Ok(r) => r,
389 Err(e) => {
390 Log::warning(1102, Some(format!("{} ({})", e, path.display())));
391 continue;
392 }
393 };
394
395 for entry in read_path {
397 let path = match entry {
398 Ok(e) => e.path(),
399 Err(e) => {
400 Log::warning(1101, Some(format!("{} ({})", e, path.display())));
401 continue;
402 }
403 };
404 if !path.is_dir() {
405 continue;
406 }
407 let class = match path.file_name() {
408 Some(c) => match c.to_str() {
409 Some(class) => class,
410 None => continue,
411 },
412 None => continue,
413 };
414 let read_path = match read_dir(&path) {
415 Ok(r) => r,
416 Err(e) => {
417 Log::warning(1102, Some(format!("{} ({})", e, path.display())));
418 continue;
419 }
420 };
421 for entry in read_path {
423 let path = match entry {
424 Ok(e) => e.path(),
425 Err(e) => {
426 Log::warning(1101, Some(format!("{} ({})", e, path.display())));
427 continue;
428 }
429 };
430 if !path.is_file() {
431 continue;
432 }
433 let code = match path.file_name() {
434 Some(v) => match v.to_str() {
435 Some(view) => view,
436 None => continue,
437 },
438 None => continue,
439 };
440 if code.starts_with("lang.") && code.ends_with(".toml") && code.len() == 12 {
441 let code = unsafe { code.get_unchecked(5..7) }.to_owned();
442 vec.push((path, module.to_owned(), class.to_owned(), code));
443 }
444 }
445 }
446 }
447 vec
448 }
449
450 #[cfg(debug_assertions)]
452 pub(crate) async fn check_time(&self) -> bool {
453 let files = Lang::get_files(Arc::clone(&self.root)).await;
454 let mut last_time = SystemTime::UNIX_EPOCH;
455 let mut hash: i128 = 0;
456
457 for (path, _, _, _) in files {
458 if let Ok(metadata) = fs::metadata(&path).await {
459 if let Ok(modified_time) = metadata.modified() {
460 if modified_time > last_time {
461 last_time = modified_time;
462 }
463 if let Some(s) = path.as_os_str().to_str() {
464 hash += fnv1a_64(s.as_bytes()) as i128;
465 }
466 }
467 }
468 }
469 last_time != self.last || hash != self.hash
470 }
471
472 pub(crate) async fn load(&mut self, files: Vec<(PathBuf, String, String, String)>) {
474 #[cfg(debug_assertions)]
475 let mut last_time = SystemTime::UNIX_EPOCH;
476 #[cfg(debug_assertions)]
477 let mut hash: i128 = 0;
478
479 let mut list = BTreeMap::new();
480
481 for (path, module, class, code) in files {
482 if let Some(id) = self.codes.get(&code) {
483 if let Ok(text) = read_to_string(&path) {
484 #[cfg(debug_assertions)]
485 if let Ok(metadata) = fs::metadata(&path).await {
486 if let Ok(modified_time) = metadata.modified() {
487 if modified_time > last_time {
488 last_time = modified_time;
489 }
490 if let Some(s) = path.as_os_str().to_str() {
491 hash += fnv1a_64(s.as_bytes()) as i128;
492 }
493 }
494 }
495 if !text.is_empty() {
496 let text = match text.parse::<Table>() {
497 Ok(v) => v,
498 Err(e) => {
499 Log::warning(19, Some(format!("{:?} {} ", path.to_str(), e)));
500 continue;
501 }
502 };
503 for (key, value) in text {
504 if let Value::String(val) = value {
505 let l1 = match list.entry(*id) {
506 Entry::Vacant(v) => v.insert(BTreeMap::new()),
507 Entry::Occupied(o) => o.into_mut(),
508 };
509 let l2 = match l1.entry(fnv1a_64(module.as_bytes())) {
511 Entry::Vacant(v) => v.insert(BTreeMap::new()),
512 Entry::Occupied(o) => o.into_mut(),
513 };
514 let l3 = match l2.entry(fnv1a_64(class.as_bytes())) {
516 Entry::Vacant(v) => v.insert(BTreeMap::new()),
517 Entry::Occupied(o) => o.into_mut(),
518 };
519 l3.insert(fnv1a_64(key.as_bytes()), val);
520 } else {
521 Log::warning(20, Some(format!("{:?} {} ", path.to_str(), value)));
522 continue;
523 }
524 }
525 }
526 }
527 }
528 }
529
530 let mut list_lang = BTreeMap::new();
532 for (key_lang, item_lang) in list {
533 let mut list_module = BTreeMap::new();
534 for (key_module, item_module) in item_lang {
535 let mut list_class = BTreeMap::new();
536 for (key_class, item_class) in item_module {
537 list_class.insert(key_class, Arc::new(item_class));
538 }
539 list_module.insert(key_module, list_class);
540 }
541 list_lang.insert(key_lang, list_module);
542 }
543 self.list = Arc::new(list_lang);
544 #[cfg(debug_assertions)]
545 {
546 self.last = last_time;
547 }
548 #[cfg(debug_assertions)]
549 {
550 self.hash = hash;
551 }
552 }
553}