1use std::fmt;
11use std::collections::HashMap;
12use std::ffi::OsStr;
13use std::path;
14use std::path::{Path, PathBuf};
15use std::sync::LazyLock;
16
17use serde::{self, Serialize, Deserialize};
18use serde_json;
19
20use super::config_collector::{ConfigCollectorDest, ConfigCollectorDestRes};
21
22
23static MAIN_SEPARATOR: LazyLock<&'static OsStr> =
24 LazyLock::new(||
25 {
26 OsStr::new(unsafe { std::str::from_utf8_unchecked(&[path::MAIN_SEPARATOR as u8]) })
27 }
28 );
29static MAIN_DDOT: LazyLock<&'static OsStr> =
30 LazyLock::new(||
31 {
32 OsStr::new("..")
33 }
34 );
35static MAIN_DOT: LazyLock<&'static OsStr> =
36 LazyLock::new(||
37 {
38 OsStr::new(".")
39 }
40 );
41
42
43
44
45#[derive(Serialize, Deserialize)]
46#[serde(crate = "self::serde")]
47pub enum CNode
48{
49 File
51 {
52 title: String,
53 content: String,
54 },
55
56 Directory
58 {
59 title: String,
60 content: HashMap<String, CNode>
61 }
62}
63
64impl fmt::Display for CNode
65{
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
67 {
68 match *self
69 {
70 Self::Directory{ref title, ..} =>
71 {
72 write!(f, "{}", title)
73 },
74 Self::File{ref title, ..} =>
75 {
76 write!(f, "{}", title)
77 }
78 }
79 }
80}
81
82impl ConfigCollectorDest for CNode
83{
84 fn insert_file(&mut self, path: &Path, content: String) -> Result<ConfigCollectorDestRes, String>
85 {
86 return self.add_file(path, content);
87 }
88
89 fn exists(&self, path: &Path) -> Result<ConfigCollectorDestRes, String>
90 {
91 return self.check(path);
92 }
93
94 fn serialize_to_string(&self) -> Result<String, String>
95 {
96 return self.serialize_to_string();
97 }
98}
99
100impl CNode
101{
102 pub
103 fn deserialize_from_string(serdata: &String) -> Result<Self, String>
104 {
105 let deser: CNode =
106 serde_json::from_str(serdata)
107 .map_err(|e| format!("{}", e))?;
108
109 return Ok(deser)
110 }
111
112 pub
113 fn new_defualt() -> Self
114 {
115 return CNode::new_directory(String::from("defualt")).unwrap();
116 }
117
118 pub
119 fn new_root<R: Into<String>>(rootdirname: R) -> Result<Self, String>
120 {
121 return CNode::new_directory(rootdirname.into());
122 }
123
124 pub
125 fn serialize_to_string(&self) -> Result<String, String>
126 {
127 return Ok(serde_json::to_string(self).map_err(|e| format!("{}", e))?);
128 }
129
130 pub
131 fn clone_title(&self) -> String
132 {
133 match *self
134 {
135 Self::Directory{ref title,..} => title.clone(),
136 Self::File{ref title,..} => title.clone()
137 }
138 }
139
140 pub
141 fn get_title(&self) -> &String
142 {
143 match *self
144 {
145 Self::Directory{ref title,..} => &title,
146 Self::File{ref title,..} => &title
147 }
148 }
149
150 #[allow(unused_variables)]
151 fn int_open(&self, cur: Option<&OsStr>, mut pathit: path::Iter<'_>) -> Result<ConfigCollectorDestRes, String>
152 {
153 match *self
154 {
155 Self::Directory{ref title, ref content} =>
156 {
157 let utf_p =
158 match cur
159 {
160 Some(r) =>
161 {
162 if r == *MAIN_SEPARATOR || r == *MAIN_DDOT || r == *MAIN_DOT
163 {
164 let cur = pathit.next();
166 return self.int_open(cur, pathit);
167 }
168
169 r.to_str().unwrap().to_string()
170 },
171 None => return Ok(ConfigCollectorDestRes::DirReadAttempt(title.clone()))
172 };
173
174 let next =
175 match content.get(&utf_p)
176 {
177 Some(p) => p,
178 None => return Ok(ConfigCollectorDestRes::NotFound(utf_p.clone()))
179 };
180
181 let pnext = pathit.next();
182
183 return next.int_open(pnext, pathit);
184 },
185 Self::File{ref title, ref content} =>
186 {
187 return Ok(ConfigCollectorDestRes::Found(content.clone()));
188 }
189 }
190 }
191
192 #[allow(unused_variables)]
194 fn int_check(&self, cur: Option<&OsStr>, mut pathit: path::Iter<'_>) -> Result<ConfigCollectorDestRes, String>
195 {
196 match *self
197 {
198 Self::Directory{ref title, ref content} =>
199 {
200 let utf_p =
201 match cur
202 {
203 Some(r) =>
204 {
205 if r == *MAIN_SEPARATOR || r == *MAIN_DDOT || r == *MAIN_DOT
206 {
207 let cur = pathit.next();
209 return self.int_check(cur, pathit);
210 }
211
212 r.to_str().unwrap().to_string()
213 },
214 None => return Ok(ConfigCollectorDestRes::FileOrDirAlreadyExists)
215 };
216
217 let next =
218 match content.get(&utf_p)
219 {
220 Some(p) => p,
221 None => return Ok(ConfigCollectorDestRes::NotFound(utf_p.clone()))
222 };
223
224 let pnext = pathit.next();
225
226 return next.int_check(pnext, pathit);
227 },
228 Self::File{ref title, ref content} =>
229 {
230 return Ok(ConfigCollectorDestRes::FileOrDirAlreadyExists);
231 }
232 }
233 }
234
235 fn new_directory(dirname: String) -> Result<Self, String>
236 {
237 if dirname.is_empty() == true
238 {
239 return Err(format!("Directory name can not be empty"));
240 }
241
242 return Ok(Self::Directory{title: dirname, content: HashMap::new()});
243 }
244
245 fn new_file(filename: String, content: Option<String>) -> Result<Self, String>
246 {
247 if filename.is_empty() == true
248 {
249 return Err(format!("Filename is empty!"));
250 }
251
252 if content.is_none() == true
253 {
254 return Err(format!("Can not create file {}, file is empty!", filename));
255 }
256
257 return Ok(Self::File{title: filename, content: content.unwrap()});
258 }
259
260 #[allow(unused_variables)]
261 fn int_add(&mut self, cur: Option<&std::ffi::OsStr>, mut pathit: std::path::Iter<'_>, filecontent: Option<String>) -> Result<ConfigCollectorDestRes, String>
262 {
263 match self
264 {
265 Self::Directory{title, content} =>
266 {
267 let (curp, utf_p) =
268 match cur
269 {
270 Some(r) =>
271 {
272 if r == *MAIN_SEPARATOR || r == *MAIN_DDOT || r == *MAIN_DOT
273 {
274 let cur = pathit.next();
276 return self.int_add(cur, pathit, filecontent);
277 }
278
279 (r, r.to_str().unwrap().to_string())
280 },
281 None =>
282 {
283 if filecontent.is_none() == true
284 {
285 return Ok(ConfigCollectorDestRes::Ok);
286 }
287 else
288 {
289 panic!("Unexpected EOF in path. Maybe missing file extension");
290 }
291 }
292 };
293
294 if content.contains_key(&utf_p) == true
295 {
296 let k = content.get_mut(&utf_p).unwrap();
297 let pnext = pathit.next();
298
299 return k.int_add(pnext, pathit, filecontent);
300 }
301 else
302 {
303
304 if let Some(filename) = Path::new(curp).extension()
306 {
307 let file = Self::new_file(utf_p, filecontent)?;
310
311 content.insert(file.clone_title(), file);
312
313 return Ok(ConfigCollectorDestRes::Ok);
314 }
315 else
316 {
317 let dir = CNode::new_directory(utf_p)?;
319 let k = content.entry(dir.clone_title()).or_insert(dir);
320
321 let pnext = pathit.next();
322
323 return k.int_add(pnext, pathit, filecontent);
324 }
325 }
326 },
327 Self::File{ref title, ref content} =>
328 {
329 return Ok(ConfigCollectorDestRes::FileOrDirAlreadyExists);
330 }
331 }
332 }
333
334 pub
335 fn add_file<P: AsRef<Path>>(&mut self, path: P, content: String) -> Result<ConfigCollectorDestRes, String>
336 {
337 let rpath = path.as_ref();
338
339 if rpath.extension().is_some() == false
341 {
342 return Err(format!("Filename must contain extension! Path: {:?}", rpath));
343 }
344
345 let mut pathit = rpath.iter();
346
347 let mut cur = pathit.next();
348
349 let ccur = cur.unwrap();
350 if ccur == *MAIN_SEPARATOR || ccur == *MAIN_DDOT || ccur == *MAIN_DOT
351 {
352 cur = pathit.next();
354 }
355
356 return self.int_add(cur, pathit, Some(content));
357
358}
360
361 pub
362 fn check<P: AsRef<Path>>(&self, path: P) -> Result<ConfigCollectorDestRes, String>
363 {
364 let rpath = path.as_ref();
365
366 let mut pathit = rpath.iter();
367
368 let mut cur = pathit.next();
369
370 let ccur = cur.unwrap();
371 if ccur == *MAIN_SEPARATOR || ccur == *MAIN_DDOT || ccur == *MAIN_DOT
372 {
373 cur = pathit.next();
375 }
376
377 return self.int_check(cur, pathit);
378 }
379
380 pub
381 fn open<P: AsRef<Path>>(&self, path: P) -> Result<String, String>
382 {
383 let rpath = path.as_ref();
384
385 if rpath.extension().is_some() == false
387 {
388 return Err(format!("Filename must contain extension! Path: {:?}", rpath));
389 }
390
391 let mut pathit = rpath.iter();
392
393 let mut cur = pathit.next();
394
395 let ccur = cur.unwrap();
396 if ccur == *MAIN_SEPARATOR || ccur == *MAIN_DDOT || ccur == *MAIN_DOT
397 {
398 cur = pathit.next();
400 }
401
402 match self.int_open(cur, pathit)?
403 {
404 ConfigCollectorDestRes::Found(r) =>
405 return Ok(r),
406 ConfigCollectorDestRes::NotFound(itm) =>
407 return Err(format!("Item not found: {}, path: {:?}, ", itm, rpath)),
408 ConfigCollectorDestRes::DirReadAttempt(itm) =>
409 return Err(format!("Attempt to open '{}' which is directory. Path: {:?}", itm, rpath)),
410 ConfigCollectorDestRes::FileOrDirAlreadyExists =>
411 panic!("ConfigCollectorDestRes::FileOrDirAlreadyExists received from int_open"),
412 ConfigCollectorDestRes::Ok =>
413 panic!("ConfigCollectorDestRes::Ok received from int_open"),
414 }
415 }
416
417 #[allow(unused_variables)]
418 pub
419 fn print_out_int(&self, mut path: PathBuf)
420 {
421 path.push(format!("{}", self));
423
424 match *self
425 {
426 Self::Directory{ref title, ref content} =>
427 {
428 for (_, ref c) in content
429 {
430 c.print_out_int(path.clone());
431 }
432
433 print!("{}\n", path.to_string_lossy());
434 },
435 Self::File{..} =>
436 {
437 print!("{}\n", path.to_string_lossy());
438 }
439 }
440
441 return;
442 }
443
444 pub
445 fn print_out(&self)
446 {
447 let path = PathBuf::new();
448
449 self.print_out_int(path);
450 }
451}
452
453#[cfg(test)]
454mod tests
455{
456 use super::*;
457
458 #[test]
459 fn test_cnode1()
460 {
461 let mut root = CNode::new_root("config").unwrap();
462
463 let res = root.add_file("action/pf.shm", String::from("config config config"));
464
465 assert_eq!(res.is_ok(), true);
466
467 let res = root.add_file("action/ipfw.shm", String::from("ipfw config"));
468
469 assert_eq!(res.is_ok(), true);
470
471 let res = root.add_file("filter/ipfw.shm", String::from("ipfw filter config"));
472
473 assert_eq!(res.is_ok(), true);
474
475 root.print_out();
476
477 return;
478 }
479
480 #[test]
481 fn test_cnode2()
482 {
483 let root = CNode::new_root("");
484
485 assert_eq!(root.is_ok(), false);
486
487 return;
488 }
489
490 #[test]
491 pub fn test_cnode3()
492 {
493 use std::time::Instant;
494
495 let mut root = CNode::new_root("config").unwrap();
496
497 let res = root.add_file("temporary", String::from("config config config 1"));
498 assert_eq!(res.is_ok(), false);
499
500 let res = root.add_file("", String::from("config config config 1"));
501 assert_eq!(res.is_ok(), false);
502
503 let res = root.add_file("action/../pf2.shm", String::from("config 2config2 config"));
504
505 assert_eq!(res.is_ok(), true);
506
507 let res = root.add_file("action/../pf2.shm", String::from("config 2config2 config"));
509
510 assert_eq!(res.is_ok(), true);
511 assert_eq!(res.unwrap().is_exists(), true);
512
513 let start = Instant::now();
514 let res = root.check("action/../pf2.shm");
515 let duration = start.elapsed();
516 println!("check action/../pf2.shm: {:?}", duration);
517
518 assert_eq!(res.is_ok(), true);
519 let rescr = res.unwrap();
520 assert_eq!(rescr.is_exists(), true);
521
522 let fff3 = String::from("pf3.shm");
523 let fff33 = String::from("pf33.shm");
524 let start = Instant::now();
525 let res = root.check("action/../pf3.shm");
526 let duration = start.elapsed();
527 println!("check action/../pf3.shm: {:?}", duration);
528 assert_eq!(res.is_ok(), true);
529 let rescr = res.unwrap();
530 assert_eq!(rescr.is_exists(), false);
531 assert_eq!(rescr.is_notfound(&fff3), true);
532 assert_eq!(rescr.is_notfound(&fff33), false);
533
534 let start = Instant::now();
535 let res = root.add_file("action/pf.shm", String::from("config config config"));
536 let duration = start.elapsed();
537 println!("Add action/pf.shm: {:?}", duration);
538 assert_eq!(res.is_ok(), true);
539
540 let file2 = String::from("ipfw config");
541 let res = root.add_file("action/ipfw.shm", file2.clone());
542
543 assert_eq!(res.is_ok(), true);
544
545 let res = root.add_file("filter/ipfw.shm", String::from("ipfw filter config"));
546
547 assert_eq!(res.is_ok(), true);
548
549 root.print_out();
550
551 let start = Instant::now();
552 let res = root.open("action/ipfw.shm");
553 let duration = start.elapsed();
554 println!("Open action/ipfw.shm: {:?}", duration);
555
556 assert_eq!(res.is_ok(), true);
557 let file2_rcv = res.unwrap();
558 assert_eq!(file2_rcv, file2);
559
560 return;
561 }
562
563}