lb_rs/model/
filename.rs

1use crate::model::file::File;
2
3pub const MAX_FILENAME_LENGTH: usize = 230;
4pub const MAX_ENCRYPTED_FILENAME_LENGTH: usize = MAX_FILENAME_LENGTH + 24;
5
6#[derive(Debug, PartialEq, Eq)]
7pub enum DocumentType {
8    Text,
9    Drawing,
10    Other,
11}
12
13// todo: be more exhaustive
14impl DocumentType {
15    pub fn from_file_name_using_extension(name: &str) -> DocumentType {
16        match name.split('.').next_back() {
17            Some("md") | Some("txt") => DocumentType::Text,
18            Some("svg") => DocumentType::Drawing,
19            _ => DocumentType::Other,
20        }
21    }
22}
23
24#[derive(PartialEq, Eq, Debug, Clone)]
25pub struct NameComponents {
26    pub name: String,
27    pub variant: Option<usize>,
28    pub extension: Option<String>,
29}
30
31impl NameComponents {
32    pub fn from(file_name: &str) -> NameComponents {
33        let extension_location = file_name.rfind('.').and_then(|location| {
34            if location == file_name.len() - 1 {
35                None
36            } else {
37                Some(location)
38            }
39        });
40
41        let name_with_variant = match extension_location {
42            Some(location) => &file_name[..location],
43            None => file_name,
44        };
45
46        let mut variant_location = name_with_variant.rfind('-');
47
48        let variant = variant_location
49            .map(|location| name_with_variant[location + 1..].to_string())
50            .and_then(|maybe_variant| maybe_variant.parse::<usize>().ok());
51
52        if variant.is_none() {
53            variant_location = None
54        }
55
56        let name = {
57            let name_right_bound =
58                variant_location.unwrap_or_else(|| extension_location.unwrap_or(file_name.len()));
59            file_name[0..name_right_bound].to_string()
60        };
61
62        let extension = extension_location.map(|location| file_name[location + 1..].to_string());
63
64        NameComponents { name, variant, extension }
65    }
66
67    pub fn generate_next(&self) -> NameComponents {
68        self.generate_incremented(1)
69    }
70
71    pub fn generate_incremented(&self, n: usize) -> NameComponents {
72        let mut next = self.clone();
73        next.variant = Some(self.variant.unwrap_or(0) + n);
74        next
75    }
76
77    pub fn next_in_children(&self, children: Vec<File>) -> NameComponents {
78        let mut next = self.clone();
79
80        let mut children: Vec<NameComponents> = children
81            .iter()
82            .filter_map(|f| {
83                let nc = NameComponents::from(&f.name);
84                if nc.name == next.name && nc.extension == next.extension {
85                    Some(nc)
86                } else {
87                    None
88                }
89            })
90            .collect();
91
92        children.sort_by(|a, b| {
93            a.variant
94                .unwrap_or_default()
95                .cmp(&b.variant.unwrap_or_default())
96        });
97
98        for (i, child) in children.iter().enumerate() {
99            if let Some(variant) = child.variant {
100                if i == 0 && variant > 0 {
101                    break; //insert self without an increment
102                }
103            }
104
105            if let Some(curr) = children.get(i + 1) {
106                if curr.variant.unwrap_or_default() != child.variant.unwrap_or_default() + 1 {
107                    next = child.generate_next();
108                    break;
109                }
110            } else {
111                next = child.generate_next();
112            }
113        }
114
115        next
116    }
117
118    pub fn to_name(&self) -> String {
119        match (&self.variant, &self.extension) {
120            (Some(variant), Some(extension)) => format!("{}-{}.{}", self.name, variant, extension),
121            (Some(variant), None) => format!("{}-{}", self.name, variant),
122            (None, Some(extension)) => format!("{}.{}", self.name, extension),
123            (None, None) => self.name.to_string(),
124        }
125    }
126}
127
128#[cfg(test)]
129mod unit_tests {
130    use uuid::Uuid;
131
132    use crate::model::filename::NameComponents;
133    use crate::model::{file::File, file_metadata::FileType};
134
135    fn from_components(
136        name: &str, variant: Option<usize>, extension: Option<&str>,
137    ) -> NameComponents {
138        NameComponents {
139            name: name.to_string(),
140            variant,
141            extension: extension.map(|str| str.to_string()),
142        }
143    }
144
145    #[test]
146    fn test_name_components() {
147        assert_eq!(NameComponents::from("test-1.md"), from_components("test", Some(1), Some("md")));
148        assert_eq!(NameComponents::from("test-.md"), from_components("test-", None, Some("md")));
149        assert_eq!(NameComponents::from(".md"), from_components("", None, Some("md")));
150        assert_eq!(NameComponents::from(""), from_components("", None, None));
151        assert_eq!(
152            NameComponents::from("test-file.md"),
153            from_components("test-file", None, Some("md"))
154        );
155        assert_eq!(
156            NameComponents::from("test-file-1.md"),
157            from_components("test-file", Some(1), Some("md"))
158        );
159        assert_eq!(
160            NameComponents::from("test-file-1.md."),
161            from_components("test-file-1.md.", None, None)
162        );
163        assert_eq!(
164            NameComponents::from("test-file-1.m"),
165            from_components("test-file", Some(1), Some("m"))
166        );
167        assert_eq!(
168            NameComponents::from("test-file-100.m"),
169            from_components("test-file", Some(100), Some("m"))
170        );
171        assert_eq!(
172            NameComponents::from("test-file--100.m"),
173            from_components("test-file-", Some(100), Some("m"))
174        );
175        assert_eq!(
176            NameComponents::from("test-file-.-100.m"),
177            from_components("test-file-.", Some(100), Some("m"))
178        );
179        assert_eq!(NameComponents::from("."), from_components(".", None, None));
180        assert_eq!(NameComponents::from("-1."), from_components("-1.", None, None));
181        assert_eq!(NameComponents::from("-1."), from_components("-1.", None, None));
182        assert_eq!(NameComponents::from("test"), from_components("test", None, None));
183        assert_eq!(NameComponents::from("test-32"), from_components("test", Some(32), None));
184    }
185
186    fn assert_symmetry(name: &str) {
187        assert_eq!(NameComponents::from(name).to_name(), name);
188    }
189
190    #[test]
191    fn test_back_to_name() {
192        assert_symmetry("test-1.md");
193        assert_symmetry("test-.md");
194        assert_symmetry(".md");
195        assert_symmetry("");
196        assert_symmetry("test-file.md");
197        assert_symmetry("test-file-1.md");
198        assert_symmetry("test-file-1.md.");
199        assert_symmetry("test-file-1.m");
200        assert_symmetry("test-file-100.m");
201        assert_symmetry("test-file--100.m");
202        assert_symmetry("test-file-.-100.m");
203        assert_symmetry(".");
204        assert_symmetry("-1.");
205        assert_symmetry("-1.");
206        assert_symmetry("test");
207        assert_symmetry("test-32");
208    }
209
210    #[test]
211    fn test_next_variant() {
212        assert_eq!(NameComponents::from("test.md").generate_next().to_name(), "test-1.md");
213        assert_eq!(NameComponents::from("test-2.md").generate_next().to_name(), "test-3.md");
214    }
215    #[test]
216    fn test_next_in_children() {
217        let children = new_files(vec!["untitled.md", "untitled-1.md", "untitled-2.md"]);
218        let next = NameComponents::from("untitled.md").next_in_children(children);
219        assert_eq!(next.to_name(), "untitled-3.md");
220
221        let children =
222            new_files(vec!["untitled-1.md", "untitled-2.md", "untitled-3.md", "untitled-4.md"]);
223        let next = NameComponents::from("untitled.md").next_in_children(children);
224        assert_eq!(next.to_name(), "untitled.md");
225
226        let children = new_files(vec!["untitled.md", "untitled-2.md", "untitled-3.md"]);
227        let next = NameComponents::from("untitled.md").next_in_children(children);
228        assert_eq!(next.to_name(), "untitled-1.md");
229    }
230
231    fn new_files(names: Vec<&str>) -> Vec<File> {
232        names
233            .iter()
234            .map(|&name| File {
235                id: Uuid::default(),
236                parent: Uuid::default(),
237                name: name.to_string(),
238                file_type: FileType::Document,
239                last_modified: u64::default(),
240                last_modified_by: String::default(),
241                shares: vec![],
242            })
243            .collect()
244    }
245}