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
13impl 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; }
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}