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 reserved.remove(entry.id());
211 for key in pool.keys() {
212 reserved.remove(key);
213 }
214 validate_subtree(
215 std::slice::from_ref(&options),
216 &reserved,
217 &mut HashSet::new(),
218 )?;
219
220 let mut options = options;
221 options.id = Some(entry.id().to_owned());
222 let group = std::mem::take(&mut options.group);
223 entry.set_options(options);
224
225 detach_child(&old_parent, &entry);
228 let children = sync_children(
229 &entry,
230 group,
231 &mut pool,
232 &reserved,
233 &mut TreeDiff::default(),
234 );
235 entry.set_children(children);
236 insert_child(&parent, entry.clone(), position);
237 Ok(entry)
238 }
239
240 pub fn update(&self, entries: Vec<EntryOptions>) -> Result<TreeDiff> {
248 let _guard = crate::lock(&self.mutation);
249 validate_subtree(&entries, &HashSet::new(), &mut HashSet::new())?;
252
253 let mut pool: HashMap<String, Entry> = self
255 .entries()
256 .into_iter()
257 .map(|entry| (entry.id().to_string(), entry))
258 .collect();
259 let reserved = HashSet::new();
260 let mut diff = TreeDiff::default();
261 let children = sync_children(&self.root, entries, &mut pool, &reserved, &mut diff);
262 self.root.set_children(children);
263 diff.removed = pool.into_values().collect();
264 Ok(diff)
265 }
266
267 fn ids(&self) -> HashSet<String> {
269 self.entries().iter().map(|e| e.id().to_string()).collect()
270 }
271
272 fn assert_owned(&self, candidate: &Entry) -> Result<()> {
274 let mut current = candidate.clone();
275 loop {
276 if Entry::ptr_eq(¤t, &self.root) {
277 return Ok(());
278 }
279 match current.parent() {
280 Some(parent) => current = parent,
281 None => return Err(IncludeError::NotInTree),
282 }
283 }
284 }
285}
286
287fn sync_children(
291 parent: &Entry,
292 options: Vec<EntryOptions>,
293 pool: &mut HashMap<String, Entry>,
294 reserved: &HashSet<String>,
295 diff: &mut TreeDiff,
296) -> Vec<Entry> {
297 let mut result = Vec::with_capacity(options.len());
298 for options in options {
299 let mut options = options;
300 let id = match options.id.take() {
301 Some(id) => id,
302 None => generate_id(reserved, pool),
303 };
304 options.id = Some(id.clone());
305 let group = std::mem::take(&mut options.group);
306 let entry = match pool.remove(&id) {
307 Some(existing) => {
308 if existing.options() != options {
309 existing.set_options(options);
310 diff.updated.push(existing.clone());
311 }
312 let moved = existing
313 .parent()
314 .is_none_or(|old| !Entry::ptr_eq(&old, parent));
315 if moved {
316 diff.moved.push(existing.clone());
317 }
318 existing
319 }
320 None => {
321 let created = Entry::new(id, options);
322 diff.created.push(created.clone());
323 created
324 }
325 };
326 let children = sync_children(&entry, group, pool, reserved, diff);
327 entry.set_children(children);
328 result.push(entry);
329 }
330 result
331}
332
333fn descendants(entry: &Entry) -> Vec<Entry> {
335 let mut out = Vec::new();
336 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
337 for child in entry.children() {
338 out.push(child.clone());
339 walk(&child, out);
340 }
341 }
342 walk(entry, &mut out);
343 out
344}
345
346fn insert_child(parent: &Entry, child: Entry, position: Option<usize>) {
349 let mut siblings = parent.children();
350 let index = position.unwrap_or(siblings.len()).min(siblings.len());
351 siblings.insert(index, child);
352 parent.set_children(siblings);
353}
354
355fn detach_child(parent: &Entry, child: &Entry) {
358 let kept: Vec<Entry> = parent
359 .children()
360 .into_iter()
361 .filter(|kept| !Entry::ptr_eq(kept, child))
362 .collect();
363 parent.set_children(kept);
364}
365
366fn validate_id(id: &str) -> Result<()> {
368 if id.is_empty() || id.contains(':') {
369 return Err(IncludeError::InvalidId { id: id.to_owned() });
370 }
371 Ok(())
372}
373
374fn validate_subtree(
378 entries: &[EntryOptions],
379 reserved: &HashSet<String>,
380 seen: &mut HashSet<String>,
381) -> Result<()> {
382 for options in entries {
383 if options.name.is_empty() {
384 return Err(IncludeError::InvalidName);
385 }
386 if let Some(id) = options.id.as_deref() {
387 validate_id(id)?;
388 if reserved.contains(id) {
389 return Err(IncludeError::DuplicateId { id: id.to_owned() });
390 }
391 if !seen.insert(id.to_owned()) {
392 return Err(IncludeError::DuplicateId { id: id.to_owned() });
393 }
394 }
395 validate_subtree(&options.group, reserved, seen)?;
396 }
397 Ok(())
398}
399
400fn generate_id(reserved: &HashSet<String>, pool: &HashMap<String, Entry>) -> String {
403 loop {
404 let candidate = random_base36_6();
405 if !reserved.contains(&candidate) && !pool.contains_key(&candidate) {
406 return candidate;
407 }
408 }
409}
410
411fn random_base36_6() -> String {
414 use std::sync::atomic::{AtomicU64, Ordering};
415 static COUNTER: AtomicU64 = AtomicU64::new(0);
416
417 let nanos = SystemTime::now()
418 .duration_since(UNIX_EPOCH)
419 .map(|elapsed| elapsed.as_nanos() as u64)
420 .unwrap_or(0);
421 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
422 let mut z = nanos ^ count.wrapping_mul(0x9E37_79B9_7F4A_7C15);
423 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
424 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
425 z ^= z >> 31;
426
427 const ALPHABET: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
428 let mut value = z % 2_176_782_336; let mut out = [0u8; 6];
430 for slot in out.iter_mut().rev() {
431 *slot = ALPHABET[(value % 36) as usize];
432 value /= 36;
433 }
434 String::from_utf8_lossy(&out).into_owned()
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn generated_ids_are_six_base36_chars() {
443 for _ in 0..100 {
444 let id = random_base36_6();
445 assert_eq!(id.len(), 6, "{id}");
446 assert!(
447 id.bytes()
448 .all(|b| b.is_ascii_digit() || b.is_ascii_lowercase())
449 );
450 }
451 }
452
453 #[test]
454 fn generated_ids_avoid_collisions() {
455 let first = random_base36_6();
456 let reserved: HashSet<String> = [first.clone()].into_iter().collect();
457 assert_ne!(generate_id(&reserved, &HashMap::new()), first);
458 }
459}