1use crate::entry::Entry;
4use crate::error::{IncludeError, Result};
5use crate::options::EntryOptions;
6use std::collections::{HashMap, HashSet};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Default, Clone)]
14pub struct TreeDiff {
15 pub created: Vec<Entry>,
17 pub updated: Vec<Entry>,
19 pub moved: Vec<Entry>,
21 pub removed: Vec<Entry>,
23}
24
25impl TreeDiff {
26 pub fn is_empty(&self) -> bool {
28 self.created.is_empty()
29 && self.updated.is_empty()
30 && self.moved.is_empty()
31 && self.removed.is_empty()
32 }
33}
34
35pub struct EntryTree {
46 root: Entry,
47 mutation: std::sync::Mutex<()>,
48}
49
50impl Default for EntryTree {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl EntryTree {
57 pub fn new() -> Self {
59 Self {
60 root: Entry::new_root(),
61 mutation: std::sync::Mutex::new(()),
62 }
63 }
64
65 pub fn root(&self) -> &Entry {
68 &self.root
69 }
70
71 pub fn top_level(&self) -> Vec<Entry> {
73 self.root.children()
74 }
75
76 pub fn entries(&self) -> Vec<Entry> {
78 let mut out = Vec::new();
79 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
80 for child in entry.children() {
81 out.push(child.clone());
82 walk(&child, out);
83 }
84 }
85 walk(&self.root, &mut out);
86 out
87 }
88
89 pub fn resolve(&self, id: &str) -> Option<Entry> {
91 let mut current = self.root.clone();
92 for part in id.split(':') {
93 current = current
94 .children()
95 .into_iter()
96 .find(|child| child.id() == part)?;
97 }
98 Some(current)
99 }
100
101 pub fn serialize(&self) -> Vec<EntryOptions> {
103 fn to_options(entry: &Entry) -> EntryOptions {
104 let mut options = entry.options();
105 options.group = entry.children().iter().map(to_options).collect();
106 options
107 }
108 self.root.children().iter().map(to_options).collect()
109 }
110
111 pub fn create(
115 &self,
116 options: EntryOptions,
117 parent: Option<&Entry>,
118 position: Option<usize>,
119 ) -> Result<Entry> {
120 let _guard = crate::lock(&self.mutation);
121 let parent = parent.unwrap_or(&self.root);
122 self.assert_owned(parent)?;
123 if options.name.is_empty() {
124 return Err(IncludeError::InvalidName);
125 }
126 let reserved: HashSet<String> = self.ids();
127 validate_subtree(
128 std::slice::from_ref(&options),
129 &reserved,
130 &mut HashSet::new(),
131 )?;
132
133 let mut options = options;
134 let id = match options.id.take() {
135 Some(id) => id,
136 None => generate_id(&reserved, &HashMap::new()),
137 };
138 let group = std::mem::take(&mut options.group);
139 options.id = Some(id.clone());
140 let entry = Entry::new(id, options);
141 let children = sync_children(
142 &entry,
143 group,
144 &mut HashMap::new(),
145 &reserved,
146 &mut TreeDiff::default(),
147 );
148 entry.set_children(children);
149 insert_child(parent, entry.clone(), position);
150 Ok(entry)
151 }
152
153 pub fn remove(&self, id: &str) -> Result<Entry> {
156 let _guard = crate::lock(&self.mutation);
157 let entry = self
158 .resolve(id)
159 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
160 let parent = entry
161 .parent()
162 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
163 detach_child(&parent, &entry);
164 Ok(entry)
165 }
166
167 pub fn update_entry(
173 &self,
174 id: &str,
175 options: EntryOptions,
176 new_parent: Option<&Entry>,
177 position: Option<usize>,
178 ) -> Result<Entry> {
179 let _guard = crate::lock(&self.mutation);
180 let entry = self
181 .resolve(id)
182 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
183 let old_parent = entry
184 .parent()
185 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
186 let parent = match new_parent {
187 Some(parent) => {
188 self.assert_owned(parent)?;
189 if entry.contains(parent) {
190 return Err(IncludeError::Cycle);
191 }
192 parent.clone()
193 }
194 None => old_parent.clone(),
195 };
196 if options.name.is_empty() {
197 return Err(IncludeError::InvalidName);
198 }
199
200 let subtree = descendants(&entry);
203 let mut pool: HashMap<String, Entry> = subtree
204 .iter()
205 .map(|child| (child.id().to_string(), child.clone()))
206 .collect();
207 let mut reserved = self.ids();
208 for key in pool.keys() {
209 reserved.remove(key);
210 }
211 validate_subtree(
212 std::slice::from_ref(&options),
213 &reserved,
214 &mut HashSet::new(),
215 )?;
216
217 let mut options = options;
218 options.id = Some(entry.id().to_owned());
219 let group = std::mem::take(&mut options.group);
220 entry.set_options(options);
221
222 detach_child(&old_parent, &entry);
225 let children = sync_children(
226 &entry,
227 group,
228 &mut pool,
229 &reserved,
230 &mut TreeDiff::default(),
231 );
232 entry.set_children(children);
233 insert_child(&parent, entry.clone(), position);
234 Ok(entry)
235 }
236
237 pub fn update(&self, entries: Vec<EntryOptions>) -> Result<TreeDiff> {
245 let _guard = crate::lock(&self.mutation);
246 validate_subtree(&entries, &HashSet::new(), &mut HashSet::new())?;
249
250 let mut pool: HashMap<String, Entry> = self
252 .entries()
253 .into_iter()
254 .map(|entry| (entry.id().to_string(), entry))
255 .collect();
256 let reserved = HashSet::new();
257 let mut diff = TreeDiff::default();
258 let children = sync_children(&self.root, entries, &mut pool, &reserved, &mut diff);
259 self.root.set_children(children);
260 diff.removed = pool.into_values().collect();
261 Ok(diff)
262 }
263
264 fn ids(&self) -> HashSet<String> {
266 self.entries().iter().map(|e| e.id().to_string()).collect()
267 }
268
269 fn assert_owned(&self, candidate: &Entry) -> Result<()> {
271 let mut current = candidate.clone();
272 loop {
273 if Entry::ptr_eq(¤t, &self.root) {
274 return Ok(());
275 }
276 match current.parent() {
277 Some(parent) => current = parent,
278 None => return Err(IncludeError::NotInTree),
279 }
280 }
281 }
282}
283
284fn sync_children(
288 parent: &Entry,
289 options: Vec<EntryOptions>,
290 pool: &mut HashMap<String, Entry>,
291 reserved: &HashSet<String>,
292 diff: &mut TreeDiff,
293) -> Vec<Entry> {
294 let mut result = Vec::with_capacity(options.len());
295 for options in options {
296 let mut options = options;
297 let id = match options.id.take() {
298 Some(id) => id,
299 None => generate_id(reserved, pool),
300 };
301 options.id = Some(id.clone());
302 let group = std::mem::take(&mut options.group);
303 let entry = match pool.remove(&id) {
304 Some(existing) => {
305 if existing.options() != options {
306 existing.set_options(options);
307 diff.updated.push(existing.clone());
308 }
309 let moved = existing
310 .parent()
311 .is_none_or(|old| !Entry::ptr_eq(&old, parent));
312 if moved {
313 diff.moved.push(existing.clone());
314 }
315 existing
316 }
317 None => {
318 let created = Entry::new(id, options);
319 diff.created.push(created.clone());
320 created
321 }
322 };
323 let children = sync_children(&entry, group, pool, reserved, diff);
324 entry.set_children(children);
325 result.push(entry);
326 }
327 result
328}
329
330fn descendants(entry: &Entry) -> Vec<Entry> {
332 let mut out = Vec::new();
333 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
334 for child in entry.children() {
335 out.push(child.clone());
336 walk(&child, out);
337 }
338 }
339 walk(entry, &mut out);
340 out
341}
342
343fn insert_child(parent: &Entry, child: Entry, position: Option<usize>) {
346 let mut siblings = parent.children();
347 let index = position.unwrap_or(siblings.len()).min(siblings.len());
348 siblings.insert(index, child);
349 parent.set_children(siblings);
350}
351
352fn detach_child(parent: &Entry, child: &Entry) {
355 let kept: Vec<Entry> = parent
356 .children()
357 .into_iter()
358 .filter(|kept| !Entry::ptr_eq(kept, child))
359 .collect();
360 parent.set_children(kept);
361}
362
363fn validate_id(id: &str) -> Result<()> {
365 if id.is_empty() || id.contains(':') {
366 return Err(IncludeError::InvalidId { id: id.to_owned() });
367 }
368 Ok(())
369}
370
371fn validate_subtree(
375 entries: &[EntryOptions],
376 reserved: &HashSet<String>,
377 seen: &mut HashSet<String>,
378) -> Result<()> {
379 for options in entries {
380 if options.name.is_empty() {
381 return Err(IncludeError::InvalidName);
382 }
383 if let Some(id) = options.id.as_deref() {
384 validate_id(id)?;
385 if reserved.contains(id) {
386 return Err(IncludeError::DuplicateId { id: id.to_owned() });
387 }
388 if !seen.insert(id.to_owned()) {
389 return Err(IncludeError::DuplicateId { id: id.to_owned() });
390 }
391 }
392 validate_subtree(&options.group, reserved, seen)?;
393 }
394 Ok(())
395}
396
397fn generate_id(reserved: &HashSet<String>, pool: &HashMap<String, Entry>) -> String {
400 loop {
401 let candidate = random_base36_6();
402 if !reserved.contains(&candidate) && !pool.contains_key(&candidate) {
403 return candidate;
404 }
405 }
406}
407
408fn random_base36_6() -> String {
411 use std::sync::atomic::{AtomicU64, Ordering};
412 static COUNTER: AtomicU64 = AtomicU64::new(0);
413
414 let nanos = SystemTime::now()
415 .duration_since(UNIX_EPOCH)
416 .map(|elapsed| elapsed.as_nanos() as u64)
417 .unwrap_or(0);
418 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
419 let mut z = nanos ^ count.wrapping_mul(0x9E37_79B9_7F4A_7C15);
420 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
421 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
422 z ^= z >> 31;
423
424 const ALPHABET: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
425 let mut value = z % 2_176_782_336; let mut out = [0u8; 6];
427 for slot in out.iter_mut().rev() {
428 *slot = ALPHABET[(value % 36) as usize];
429 value /= 36;
430 }
431 String::from_utf8_lossy(&out).into_owned()
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn generated_ids_are_six_base36_chars() {
440 for _ in 0..100 {
441 let id = random_base36_6();
442 assert_eq!(id.len(), 6, "{id}");
443 assert!(
444 id.bytes()
445 .all(|b| b.is_ascii_digit() || b.is_ascii_lowercase())
446 );
447 }
448 }
449
450 #[test]
451 fn generated_ids_avoid_collisions() {
452 let first = random_base36_6();
453 let reserved: HashSet<String> = [first.clone()].into_iter().collect();
454 assert_ne!(generate_id(&reserved, &HashMap::new()), first);
455 }
456}