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 { 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; }
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}