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