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>,
30 pub moved: Vec<Entry>,
32 pub removed: Vec<RemovedEntry>,
34}
35
36impl TreeDiff {
37 pub fn is_empty(&self) -> bool {
39 self.created.is_empty()
40 && self.updated.is_empty()
41 && self.moved.is_empty()
42 && self.removed.is_empty()
43 }
44}
45
46pub struct EntryTree {
57 root: Entry,
58 mutation: std::sync::Mutex<()>,
59}
60
61impl Default for EntryTree {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl EntryTree {
68 pub fn new() -> Self {
70 Self {
71 root: Entry::new_root(),
72 mutation: std::sync::Mutex::new(()),
73 }
74 }
75
76 pub fn root(&self) -> &Entry {
79 &self.root
80 }
81
82 pub fn top_level(&self) -> Vec<Entry> {
84 self.root.children()
85 }
86
87 pub fn entries(&self) -> Vec<Entry> {
89 let mut out = Vec::new();
90 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
91 for child in entry.children() {
92 out.push(child.clone());
93 walk(&child, out);
94 }
95 }
96 walk(&self.root, &mut out);
97 out
98 }
99
100 pub fn resolve(&self, id: &str) -> Option<Entry> {
102 let mut current = self.root.clone();
103 for part in id.split(':') {
104 current = current
105 .children()
106 .into_iter()
107 .find(|child| child.id() == part)?;
108 }
109 Some(current)
110 }
111
112 pub fn serialize(&self) -> Vec<EntryOptions> {
114 fn to_options(entry: &Entry) -> EntryOptions {
115 let mut options = entry.options();
116 options.group = entry.children().iter().map(to_options).collect();
117 options
118 }
119 self.root.children().iter().map(to_options).collect()
120 }
121
122 pub fn create(
126 &self,
127 options: EntryOptions,
128 parent: Option<&Entry>,
129 position: Option<usize>,
130 ) -> Result<Entry> {
131 let _guard = crate::lock(&self.mutation);
132 let parent = parent.unwrap_or(&self.root);
133 self.assert_owned(parent)?;
134 if options.name.is_empty() {
135 return Err(IncludeError::InvalidName);
136 }
137 let reserved: HashSet<String> = self.ids();
138 validate_subtree(
139 std::slice::from_ref(&options),
140 &reserved,
141 &mut HashSet::new(),
142 )?;
143
144 let mut options = options;
145 let id = match options.id.take() {
146 Some(id) => id,
147 None => generate_id(&reserved, &HashMap::new()),
148 };
149 let group = std::mem::take(&mut options.group);
150 options.id = Some(id.clone());
151 let entry = Entry::new(id, options);
152 let children = sync_children(
153 &entry,
154 group,
155 &mut HashMap::new(),
156 &reserved,
157 &mut TreeDiff::default(),
158 );
159 entry.set_children(children);
160 insert_child(parent, entry.clone(), position);
161 Ok(entry)
162 }
163
164 pub fn remove(&self, id: &str) -> Result<Entry> {
167 let _guard = crate::lock(&self.mutation);
168 let entry = self
169 .resolve(id)
170 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
171 let parent = entry
172 .parent()
173 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
174 detach_child(&parent, &entry);
175 Ok(entry)
176 }
177
178 pub fn update_entry(
184 &self,
185 id: &str,
186 options: EntryOptions,
187 new_parent: Option<&Entry>,
188 position: Option<usize>,
189 ) -> Result<Entry> {
190 let _guard = crate::lock(&self.mutation);
191 let entry = self
192 .resolve(id)
193 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
194 let old_parent = entry
195 .parent()
196 .ok_or_else(|| IncludeError::EntryNotFound { id: id.to_owned() })?;
197 let parent = match new_parent {
198 Some(parent) => {
199 self.assert_owned(parent)?;
200 if entry.contains(parent) {
201 return Err(IncludeError::Cycle);
202 }
203 parent.clone()
204 }
205 None => old_parent.clone(),
206 };
207 if options.name.is_empty() {
208 return Err(IncludeError::InvalidName);
209 }
210
211 let subtree = descendants(&entry);
214 let mut pool: HashMap<String, Entry> = subtree
215 .iter()
216 .map(|child| (child.id().to_string(), child.clone()))
217 .collect();
218 let mut reserved = self.ids();
219 reserved.remove(entry.id());
222 for key in pool.keys() {
223 reserved.remove(key);
224 }
225 validate_subtree(
226 std::slice::from_ref(&options),
227 &reserved,
228 &mut HashSet::new(),
229 )?;
230
231 let mut options = options;
232 options.id = Some(entry.id().to_owned());
233 let group = std::mem::take(&mut options.group);
234 entry.set_options(options);
235
236 detach_child(&old_parent, &entry);
239 let children = sync_children(
240 &entry,
241 group,
242 &mut pool,
243 &reserved,
244 &mut TreeDiff::default(),
245 );
246 entry.set_children(children);
247 insert_child(&parent, entry.clone(), position);
248 Ok(entry)
249 }
250
251 pub fn update(&self, entries: Vec<EntryOptions>) -> Result<TreeDiff> {
259 let _guard = crate::lock(&self.mutation);
260 validate_subtree(&entries, &HashSet::new(), &mut HashSet::new())?;
263
264 let paths: HashMap<String, String> = self
267 .entries()
268 .iter()
269 .map(|entry| (entry.id().to_string(), entry.path()))
270 .collect();
271 let mut pool: HashMap<String, Entry> = self
273 .entries()
274 .into_iter()
275 .map(|entry| (entry.id().to_string(), entry))
276 .collect();
277 let reserved = HashSet::new();
278 let mut diff = TreeDiff::default();
279 let children = sync_children(&self.root, entries, &mut pool, &reserved, &mut diff);
280 self.root.set_children(children);
281 diff.removed = pool
282 .into_values()
283 .map(|entry| RemovedEntry {
284 path: paths.get(entry.id()).cloned().unwrap_or_default(),
285 entry,
286 })
287 .collect();
288 Ok(diff)
289 }
290
291 fn ids(&self) -> HashSet<String> {
293 self.entries().iter().map(|e| e.id().to_string()).collect()
294 }
295
296 fn assert_owned(&self, candidate: &Entry) -> Result<()> {
298 let mut current = candidate.clone();
299 loop {
300 if Entry::ptr_eq(¤t, &self.root) {
301 return Ok(());
302 }
303 match current.parent() {
304 Some(parent) => current = parent,
305 None => return Err(IncludeError::NotInTree),
306 }
307 }
308 }
309}
310
311fn sync_children(
315 parent: &Entry,
316 options: Vec<EntryOptions>,
317 pool: &mut HashMap<String, Entry>,
318 reserved: &HashSet<String>,
319 diff: &mut TreeDiff,
320) -> Vec<Entry> {
321 let mut result = Vec::with_capacity(options.len());
322 for options in options {
323 let mut options = options;
324 let id = match options.id.take() {
325 Some(id) => id,
326 None => generate_id(reserved, pool),
327 };
328 options.id = Some(id.clone());
329 let group = std::mem::take(&mut options.group);
330 let entry = match pool.remove(&id) {
331 Some(existing) => {
332 if existing.options() != options {
333 existing.set_options(options);
334 diff.updated.push(existing.clone());
335 }
336 let moved = existing
337 .parent()
338 .is_none_or(|old| !Entry::ptr_eq(&old, parent));
339 if moved {
340 diff.moved.push(existing.clone());
341 }
342 existing
343 }
344 None => {
345 let created = Entry::new(id, options);
346 diff.created.push(created.clone());
347 created
348 }
349 };
350 let children = sync_children(&entry, group, pool, reserved, diff);
351 entry.set_children(children);
352 result.push(entry);
353 }
354 result
355}
356
357fn descendants(entry: &Entry) -> Vec<Entry> {
359 let mut out = Vec::new();
360 fn walk(entry: &Entry, out: &mut Vec<Entry>) {
361 for child in entry.children() {
362 out.push(child.clone());
363 walk(&child, out);
364 }
365 }
366 walk(entry, &mut out);
367 out
368}
369
370fn insert_child(parent: &Entry, child: Entry, position: Option<usize>) {
373 let mut siblings = parent.children();
374 let index = position.unwrap_or(siblings.len()).min(siblings.len());
375 siblings.insert(index, child);
376 parent.set_children(siblings);
377}
378
379fn detach_child(parent: &Entry, child: &Entry) {
382 let kept: Vec<Entry> = parent
383 .children()
384 .into_iter()
385 .filter(|kept| !Entry::ptr_eq(kept, child))
386 .collect();
387 parent.set_children(kept);
388}
389
390fn validate_id(id: &str) -> Result<()> {
392 if id.is_empty() || id.contains(':') {
393 return Err(IncludeError::InvalidId { id: id.to_owned() });
394 }
395 Ok(())
396}
397
398fn validate_subtree(
402 entries: &[EntryOptions],
403 reserved: &HashSet<String>,
404 seen: &mut HashSet<String>,
405) -> Result<()> {
406 for options in entries {
407 if options.name.is_empty() {
408 return Err(IncludeError::InvalidName);
409 }
410 if let Some(id) = options.id.as_deref() {
411 validate_id(id)?;
412 if reserved.contains(id) {
413 return Err(IncludeError::DuplicateId { id: id.to_owned() });
414 }
415 if !seen.insert(id.to_owned()) {
416 return Err(IncludeError::DuplicateId { id: id.to_owned() });
417 }
418 }
419 validate_subtree(&options.group, reserved, seen)?;
420 }
421 Ok(())
422}
423
424fn generate_id(reserved: &HashSet<String>, pool: &HashMap<String, Entry>) -> String {
427 loop {
428 let candidate = random_base36_6();
429 if !reserved.contains(&candidate) && !pool.contains_key(&candidate) {
430 return candidate;
431 }
432 }
433}
434
435fn random_base36_6() -> String {
438 use std::sync::atomic::{AtomicU64, Ordering};
439 static COUNTER: AtomicU64 = AtomicU64::new(0);
440
441 let nanos = SystemTime::now()
442 .duration_since(UNIX_EPOCH)
443 .map(|elapsed| elapsed.as_nanos() as u64)
444 .unwrap_or(0);
445 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
446 let mut z = nanos ^ count.wrapping_mul(0x9E37_79B9_7F4A_7C15);
447 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
448 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
449 z ^= z >> 31;
450
451 const ALPHABET: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
452 let mut value = z % 2_176_782_336; let mut out = [0u8; 6];
454 for slot in out.iter_mut().rev() {
455 *slot = ALPHABET[(value % 36) as usize];
456 value /= 36;
457 }
458 String::from_utf8_lossy(&out).into_owned()
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn generated_ids_are_six_base36_chars() {
467 for _ in 0..100 {
468 let id = random_base36_6();
469 assert_eq!(id.len(), 6, "{id}");
470 assert!(
471 id.bytes()
472 .all(|b| b.is_ascii_digit() || b.is_ascii_lowercase())
473 );
474 }
475 }
476
477 #[test]
478 fn generated_ids_avoid_collisions() {
479 let first = random_base36_6();
480 let reserved: HashSet<String> = [first.clone()].into_iter().collect();
481 assert_ne!(generate_id(&reserved, &HashMap::new()), first);
482 }
483}