shm_rs/loader/
config_cnode.rs

1/*-
2 * shm-rs - a scheme serialization lib
3 * Copyright (C) 2021  Aleksandr Morozov
4 * 
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 *  file, You can obtain one at https://mozilla.org/MPL/2.0/.
8 */
9
10use 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    /// A file CNode
50    File
51    {
52        title: String,
53        content: String,
54    },
55
56    /// A directory CNode
57    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                                // skip /
165                                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    /// returns FileOrDirAlreadyExists when dir or file exists, NotFound when either does not exist
193    #[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                                // skip /
208                                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                                // skip /
275                                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                    //create new cnode
305                    if let Some(filename) = Path::new(curp).extension()
306                    {
307                        //this is a file
308
309                        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                        //this is directory
318                        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        // check if filename contains extension
340        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            // skip /
353            cur = pathit.next();
354        }
355
356        return self.int_add(cur, pathit, Some(content));
357
358//        return Ok(String::new());
359    }
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            // skip /
374            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        // check if filename contains extension
386        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            // skip /
399            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        //print!("/{}", self);
422        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        // test if duplicate file
508        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}