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, Clone)]
13pub struct RemovedEntry {
14 pub entry: Entry,
16 pub path: String,
18}
19
20#[derive(Debug, Default, Clone)]
25pub struct TreeDiff {
26 pub created: Vec<Entry>,
28 pub updated: Vec<Entry>,
31 pub redefined: Vec<Entry>,
35 pub moved: Vec<Entry>,
37 pub removed: Vec<RemovedEntry>,
39}
40
41impl TreeDiff {
42 pub fn is_empty(&self) -> bool {
44 self.created.is_empty()
45 && self.updated.is_empty()
46 && self.redefined.is_empty()
47 && self.moved.is_empty()
48 && self.removed.is_empty()
49 }
50}
51
52pub struct EntryTree {
63 root: Entry,
64 mutation: std::sync::Mutex<()>,
65}
66
67impl Default for EntryTree {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl EntryTree {
74 pub fn new() -> Self {
76 Self {
77 root: Entry::new_root(),
78 mutation: std::sync::Mutex::new(()),
79 }
80 }
81
82 pub fn root(&self) -> &Entry {
85 &self.root
86 }
87
88 pub fn top_level(&self) -> Vec<Entry> {
90 self.root.children()
91 }
92
93 pub fn entries(&self) -> Vec<Entry> {
95 let mut out = Vec::new();
96 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
97 for child in entry.children() {
98 out.push(child.clone());
99 walk(&child, out);
100 }
101 }
102 walk(&self.root, &mut out);
103 out
104 }
105
106 pub fn resolve(&self, id: &str) -> Option<Entry> {
108 let mut current = self.root.clone();
109 for part in id.split(':') {
110 current = current
111 .children()
112 .into_iter()
113 .find(|child| child.id() == part)?;
114 }
115 Some(current)
116 }
117
118 pub fn serialize(&self) -> Vec<EntryOptions> {
120 fn to_options(entry: &Entry) -> EntryOptions {
121 let mut options = entry.options();
122 options.group = entry.children().iter().map(to_options).collect();
123 options
124 }
125 self.root.children().iter().map(to_options).collect()
126 }
127
128 pub fn create(
132 &self,
133 options: EntryOptions,
134 parent: Option<&Entry>,
135 position: Option<usize>,
136 ) -> Result<Entry> {
137 let _guard = crate::lock(&self.mutation);
138 let parent = parent.unwrap_or(&self.root);
139 self.assert_owned(parent)?;
140 if options.name.is_empty() {
141 return Err(IncludeError::InvalidName);
142 }
143 let reserved: HashSet<String> = self.ids();
144 validate_subtree(
145 std::slice::from_ref(&options),
146 &reserved,
147 &mut HashSet::new(),
148 )?;
149
150 let mut options = options;
151 let id = match options.id.take() {
152 Some(id) => id,
153 None => generate_id(&reserved, &HashMap::new()),
154 };
155 let group = std::mem::take(&mut options.group);
156 options.id = Some(id.clone());
157 let entry = Entry::new(id, options);
158 let children = sync_children(
159 &entry,
160 group,
161 &mut HashMap::new(),
162 &reserved,
163 &mut TreeDiff::default(),
164 );
165 entry.set_children(children);
166 insert_child(parent, entry.clone(), position);
167 Ok(entry)
168 }
169
170 pub fn remove(&self, id: &str) -> Result<Entry> {
173 let _guard = crate::lock(&self.mutation);
174 let entry = self
175 .resolve(id)
176 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
177 let parent = entry
178 .parent()
179 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
180 detach_child(&parent, &entry);
181 Ok(entry)
182 }
183
184 pub fn update_entry(
190 &self,
191 id: &str,
192 options: EntryOptions,
193 new_parent: Option<&Entry>,
194 position: Option<usize>,
195 ) -> Result<Entry> {
196 let _guard = crate::lock(&self.mutation);
197 let entry = self
198 .resolve(id)
199 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
200 let old_parent = entry
201 .parent()
202 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
203 let parent = match new_parent {
204 Some(parent) => {
205 self.assert_owned(parent)?;
206 if entry.contains(parent) {
207 return Err(IncludeError::Cycle);
208 }
209 parent.clone()
210 }
211 None => old_parent.clone(),
212 };
213 if options.name.is_empty() {
214 return Err(IncludeError::InvalidName);
215 }
216
217 let subtree = descendants(&entry);
220 let mut pool: HashMap<String, Entry> = subtree
221 .iter()
222 .map(|child| (child.id().to_string(), child.clone()))
223 .collect();
224 let mut reserved = self.ids();
225 reserved.remove(entry.id());
228 for key in pool.keys() {
229 reserved.remove(key);
230 }
231 validate_subtree(
232 std::slice::from_ref(&options),
233 &reserved,
234 &mut HashSet::new(),
235 )?;
236
237 let mut options = options;
238 options.id = Some(entry.id().to_owned());
239 let group = std::mem::take(&mut options.group);
240 entry.set_options(options);
241
242 detach_child(&old_parent, &entry);
245 let children = sync_children(
246 &entry,
247 group,
248 &mut pool,
249 &reserved,
250 &mut TreeDiff::default(),
251 );
252 entry.set_children(children);
253 insert_child(&parent, entry.clone(), position);
254 Ok(entry)
255 }
256
257 pub fn update(&self, entries: Vec<EntryOptions>) -> Result<TreeDiff> {
265 let _guard = crate::lock(&self.mutation);
266 validate_subtree(&entries, &HashSet::new(), &mut HashSet::new())?;
269
270 let paths: HashMap<String, String> = self
273 .entries()
274 .iter()
275 .map(|entry| (entry.id().to_string(), entry.path()))
276 .collect();
277 let mut pool: HashMap<String, Entry> = self
279 .entries()
280 .into_iter()
281 .map(|entry| (entry.id().to_string(), entry))
282 .collect();
283 let reserved = HashSet::new();
284 let mut diff = TreeDiff::default();
285 let children = sync_children(&self.root, entries, &mut pool, &reserved, &mut diff);
286 self.root.set_children(children);
287 diff.removed = pool
288 .into_values()
289 .map(|entry| RemovedEntry {
290 path: paths.get(entry.id()).cloned().unwrap_or_default(),
291 entry,
292 })
293 .collect();
294 Ok(diff)
295 }
296
297 fn ids(&self) -> HashSet<String> {
299 self.entries().iter().map(|e| e.id().to_string()).collect()
300 }
301
302 fn assert_owned(&self, candidate: &Entry) -> Result<()> {
304 let mut current = candidate.clone();
305 loop {
306 if Entry::ptr_eq(¤t, &self.root) {
307 return Ok(());
308 }
309 match current.parent() {
310 Some(parent) => current = parent,
311 None => return Err(IncludeError::NotInTree),
312 }
313 }
314 }
315}
316
317fn sync_children(
321 parent: &Entry,
322 options: Vec<EntryOptions>,
323 pool: &mut HashMap<String, Entry>,
324 reserved: &HashSet<String>,
325 diff: &mut TreeDiff,
326) -> Vec<Entry> {
327 let mut result = Vec::with_capacity(options.len());
328 for options in options {
329 let mut options = options;
330 let id = match options.id.take() {
331 Some(id) => id,
332 None => generate_id(reserved, pool),
333 };
334 options.id = Some(id.clone());
335 let group = std::mem::take(&mut options.group);
336 let entry = match pool.remove(&id) {
337 Some(existing) => {
338 if existing.options() != options {
339 let structural = existing.options().name != options.name
342 || existing.options().inject != options.inject
343 || existing.options().disabled != options.disabled;
344 existing.set_options(options);
345 if structural {
346 diff.redefined.push(existing.clone());
347 } else {
348 diff.updated.push(existing.clone());
349 }
350 }
351 let moved = existing
352 .parent()
353 .is_none_or(|old| !Entry::ptr_eq(&old, parent));
354 if moved {
355 diff.moved.push(existing.clone());
356 }
357 existing
358 }
359 None => {
360 let created = Entry::new(id, options);
361 diff.created.push(created.clone());
362 created
363 }
364 };
365 let children = sync_children(&entry, group, pool, reserved, diff);
366 entry.set_children(children);
367 result.push(entry);
368 }
369 result
370}
371
372fn descendants(entry: &Entry) -> Vec<Entry> {
374 let mut out = Vec::new();
375 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
376 for child in entry.children() {
377 out.push(child.clone());
378 walk(&child, out);
379 }
380 }
381 walk(entry, &mut out);
382 out
383}
384
385fn insert_child(parent: &Entry, child: Entry, position: Option<usize>) {
388 let mut siblings = parent.children();
389 let index = position.unwrap_or(siblings.len()).min(siblings.len());
390 siblings.insert(index, child);
391 parent.set_children(siblings);
392}
393
394fn detach_child(parent: &Entry, child: &Entry) {
397 let kept: Vec<Entry> = parent
398 .children()
399 .into_iter()
400 .filter(|kept| !Entry::ptr_eq(kept, child))
401 .collect();
402 parent.set_children(kept);
403}
404
405fn validate_id(id: &str) -> Result<()> {
407 if id.is_empty() || id.contains(':') {
408 return Err(IncludeError::InvalidId { id: id.to_owned() });
409 }
410 Ok(())
411}
412
413fn validate_subtree(
417 entries: &[EntryOptions],
418 reserved: &HashSet<String>,
419 seen: &mut HashSet<String>,
420) -> Result<()> {
421 for options in entries {
422 if options.name.is_empty() {
423 return Err(IncludeError::InvalidName);
424 }
425 if let Some(id) = options.id.as_deref() {
426 validate_id(id)?;
427 if reserved.contains(id) {
428 return Err(IncludeError::DuplicateId { id: id.to_owned() });
429 }
430 if !seen.insert(id.to_owned()) {
431 return Err(IncludeError::DuplicateId { id: id.to_owned() });
432 }
433 }
434 validate_subtree(&options.group, reserved, seen)?;
435 }
436 Ok(())
437}
438
439fn generate_id(reserved: &HashSet<String>, pool: &HashMap<String, Entry>) -> String {
442 loop {
443 let candidate = random_base36_6();
444 if !reserved.contains(&candidate) && !pool.contains_key(&candidate) {
445 return candidate;
446 }
447 }
448}
449
450fn random_base36_6() -> String {
453 use std::sync::atomic::{AtomicU64, Ordering};
454 static COUNTER: AtomicU64 = AtomicU64::new(0);
455
456 let nanos = SystemTime::now()
457 .duration_since(UNIX_EPOCH)
458 .map(|elapsed| elapsed.as_nanos() as u64)
459 .unwrap_or(0);
460 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
461 let mut z = nanos ^ count.wrapping_mul(0x9E37_79B9_7F4A_7C15);
462 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
463 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
464 z ^= z >> 31;
465
466 const ALPHABET: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
467 let mut value = z % 2_176_782_336; let mut out = [0u8; 6];
469 for slot in out.iter_mut().rev() {
470 *slot = ALPHABET[(value % 36) as usize];
471 value /= 36;
472 }
473 String::from_utf8_lossy(&out).into_owned()
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn generated_ids_are_six_base36_chars() {
482 for _ in 0..100 {
483 let id = random_base36_6();
484 assert_eq!(id.len(), 6, "{id}");
485 assert!(
486 id.bytes()
487 .all(|b| b.is_ascii_digit() || b.is_ascii_lowercase())
488 );
489 }
490 }
491
492 #[test]
493 fn generated_ids_avoid_collisions() {
494 let first = random_base36_6();
495 let reserved: HashSet<String> = [first.clone()].into_iter().collect();
496 assert_ne!(generate_id(&reserved, &HashMap::new()), first);
497 }
498}