1use crate::db::*;
2use chrono::NaiveDateTime;
3use std::collections::{HashMap, VecDeque};
4use uuid::Uuid;
5
6#[derive(Debug, Clone)]
7pub enum MergeEventType {
8 EntryCreated,
9 EntryDeleted,
10 EntryLocationUpdated,
11 EntryUpdated,
12
13 GroupCreated,
14 GroupDeleted,
15 GroupLocationUpdated,
16 GroupUpdated,
17
18 IconCreated,
19 IconUpdated,
20}
21
22#[derive(Debug, Clone)]
23pub struct MergeEvent {
24 pub node_uuid: Uuid,
27
28 pub event_type: MergeEventType,
29}
30
31#[derive(Debug, Default, Clone)]
32pub struct MergeLog {
33 pub warnings: Vec<String>,
34 pub events: Vec<MergeEvent>,
35}
36
37#[derive(thiserror::Error, Debug)]
39pub enum MergeError {
40 #[error("{0}")]
41 GenericError(String),
42
43 #[error("Could not find group at {0:?}")]
44 FindGroupError(Vec<Uuid>),
45
46 #[error("Could not find entry at {0:?}")]
47 FindEntryError(Vec<Uuid>),
48
49 #[error("Entries with UUID {0} have the same modification time but have diverged.")]
50 EntryModificationTimeNotUpdated(String),
51
52 #[error("Groups with UUID {0} have the same modification time but have diverged.")]
53 GroupModificationTimeNotUpdated(String),
54
55 #[error("Found history entries with the same timestamp ({0}) for entry {1}.")]
56 DuplicateHistoryEntries(String, String),
57}
58
59impl MergeLog {
60 pub fn merge_with(&self, other: &MergeLog) -> MergeLog {
61 let mut response = MergeLog::default();
62 response.warnings.append(self.warnings.clone().as_mut());
63 response.warnings.append(other.warnings.clone().as_mut());
64 response.events.append(self.events.clone().as_mut());
65 response.events.append(other.events.clone().as_mut());
66 response
67 }
68
69 pub fn append(&mut self, other: &MergeLog) {
70 self.warnings.append(other.warnings.clone().as_mut());
71 self.events.append(other.events.clone().as_mut());
72 }
73}
74
75impl Database {
76 pub fn merge(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
80 let mut log = MergeLog::default();
81 log.append(&self.merge_group(&[], &other.root, false)?);
82 log.append(&self.merge_icons(other)?);
83 log.append(&self.merge_deletions(other)?);
84 Ok(log)
85 }
86
87 fn merge_icons(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
88 let mut log = MergeLog::default();
89
90 for (uuid, source_icon) in &other.meta.custom_icons {
91 let Some(destination_icon) = self.meta.custom_icons.get_mut(uuid) else {
92 if let Some(Some(deletion_time)) = self.deleted_objects.get(uuid)
93 && source_icon.last_modification_time.is_none_or(|modified| modified <= *deletion_time)
94 {
95 continue;
96 }
97
98 self.meta.custom_icons.insert(*uuid, source_icon.clone());
99 log.events.push(MergeEvent {
100 node_uuid: *uuid,
101 event_type: MergeEventType::IconCreated,
102 });
103 continue;
104 };
105
106 let source_is_newer = match (source_icon.last_modification_time, destination_icon.last_modification_time) {
107 (Some(source), Some(destination)) => source > destination,
108 (Some(_), None) => true,
109 _ => false,
110 };
111 if source_is_newer {
112 *destination_icon = source_icon.clone();
113 log.events.push(MergeEvent {
114 node_uuid: *uuid,
115 event_type: MergeEventType::IconUpdated,
116 });
117 }
118 }
119
120 Ok(log)
121 }
122
123 fn merge_deletions(&mut self, other: &Database) -> Result<MergeLog, MergeError> {
124 let is_in_deleted_queue = |uuid: Uuid, deleted_groups_queue: &VecDeque<(Uuid, NaiveDateTime)>| -> bool {
126 for (deleted_uuid, _) in deleted_groups_queue {
127 if *deleted_uuid == uuid {
129 return true;
130 }
131 }
132 false
133 };
134 let mut log = MergeLog::default();
135 let mut new_deleted_objects = self.deleted_objects.clone();
136 for (uuid, deletion_time) in &other.deleted_objects {
138 let deletion_time = deletion_time.unwrap_or_else(Times::now);
139 if new_deleted_objects.contains_key(uuid) {
140 continue;
141 }
142 let entry_location = match Self::find_node_location(&self.root, *uuid) {
143 Some(l) => l,
144 None => continue,
145 };
146 let parent_group = Group::find_group(&self.root, &entry_location).ok_or(MergeError::FindGroupError(entry_location))?;
147
148 let entry = match Group::find_entry(&parent_group, &[*uuid]) {
149 Some(e) => e,
150 None => continue,
152 };
153
154 let entry_last_modification = match with_node::<Entry, _, _>(&entry, |e| e.get_times().get_last_modification()).unwrap() {
155 Some(t) => t,
156 None => {
157 log.warnings.push(format!(
158 "Entry {} did not have a last modification timestamp",
159 entry.borrow().downcast_ref::<Entry>().unwrap().uuid
160 ));
161 Times::now()
162 }
163 };
164 if entry_last_modification < deletion_time {
165 with_node_mut::<Group, _, _>(&parent_group, |pg| pg.remove_node(*uuid)).unwrap()?;
166 log.events.push(MergeEvent {
167 event_type: MergeEventType::EntryDeleted,
168 node_uuid: *uuid,
169 });
170 new_deleted_objects.insert(*uuid, Some(deletion_time));
171 }
172 }
173 let mut deleted_groups_queue: VecDeque<(Uuid, NaiveDateTime)> = VecDeque::new();
174 for (uuid, deletion_time) in &other.deleted_objects {
175 if new_deleted_objects.contains_key(uuid) {
176 continue;
177 }
178 deleted_groups_queue.push_back((*uuid, deletion_time.unwrap_or_else(Times::now)));
179 }
180 while !deleted_groups_queue.is_empty() {
181 let (deleted_uuid, deletion_time) = deleted_groups_queue.pop_front().unwrap();
182 if new_deleted_objects.contains_key(&deleted_uuid) {
183 continue;
184 }
185 let group_location = match Self::find_node_location(&self.root, deleted_uuid) {
186 Some(l) => l,
187 None => continue,
188 };
189 let parent_group = Group::find_group(&self.root, &group_location).ok_or(MergeError::FindGroupError(group_location))?;
190
191 let group = match Group::find_group(&parent_group, &[deleted_uuid]) {
192 Some(g) => g,
193 None => {
194 continue;
197 }
198 };
199 if !with_node::<Group, _, _>(&group, |g| g.entries()).unwrap().is_empty() {
201 continue;
202 }
203 if with_node::<Group, _, _>(&group, |g| {
206 g.groups()
207 .iter()
208 .any(|child| is_in_deleted_queue(child.borrow().get_uuid(), &deleted_groups_queue))
209 })
210 .unwrap()
211 {
212 deleted_groups_queue.push_back((deleted_uuid, deletion_time));
213 continue;
214 }
215 if !with_node::<Group, _, _>(&group, |g| g.groups()).unwrap().is_empty() {
217 continue;
218 }
219 let group_last_modification = match with_node::<Group, _, _>(&group, |g| g.get_times().get_last_modification()).unwrap() {
220 Some(t) => t,
221 None => {
222 log.warnings.push(format!(
223 "Group {} did not have a last modification timestamp",
224 group.borrow().downcast_ref::<Group>().unwrap().uuid
225 ));
226 Times::now()
227 }
228 };
229 if group_last_modification < deletion_time {
230 with_node_mut::<Group, _, _>(&parent_group, |pg| pg.remove_node(deleted_uuid)).unwrap()?;
231 log.events.push(MergeEvent {
232 event_type: MergeEventType::GroupDeleted,
233 node_uuid: deleted_uuid,
234 });
235 new_deleted_objects.insert(deleted_uuid, Some(deletion_time));
236 }
237 }
238 self.deleted_objects = new_deleted_objects;
239 Ok(log)
240 }
241
242 pub(crate) fn find_node_location(root: &NodePtr, id: Uuid) -> Option<Vec<Uuid>> {
243 for node in &group_get_children(root).unwrap_or_default() {
246 let node_uuid = node.borrow().get_uuid();
247 if node_is_entry(node) {
248 if node_uuid == id {
249 return Some(vec![]);
252 }
253 } else if node_is_group(node) {
254 if node_uuid == id {
255 return Some(vec![]);
258 }
259 #[allow(unused_mut)]
260 if let Some(mut location) = Group::find_node_location(node, id) {
261 return Some(location);
264 }
265 }
266 }
267 None
268 }
269
270 fn merge_group(&self, current_group_path: &[Uuid], current_group: &NodePtr, is_in_deleted_group: bool) -> Result<MergeLog, MergeError> {
271 let mut log = MergeLog::default();
272 if let Some(destination_group_location) = Self::find_node_location(&self.root, current_group.borrow().get_uuid()) {
273 let mut destination_group_path = destination_group_location.clone();
274 destination_group_path.push(current_group.borrow().get_uuid());
275 let destination_group =
276 Group::find_group(&self.root, &destination_group_path).ok_or(MergeError::FindGroupError(destination_group_path))?;
277 let group_update_merge_events = Group::merge_with(&destination_group, current_group)?;
278 log.append(&group_update_merge_events);
279 }
280 for other_entry in &with_node::<Group, _, _>(current_group, |g| g.entries()).unwrap() {
281 let other_entry_uuid = other_entry.borrow().get_uuid();
282 let destination_entry_location = Self::find_node_location(&self.root, other_entry_uuid);
284 if let Some(destination_entry_location) = destination_entry_location {
286 let mut existing_entry_location = destination_entry_location.clone();
287 existing_entry_location.push(other_entry_uuid);
288 let existing_entry = Group::find_entry(&self.root, &existing_entry_location)
291 .ok_or(MergeError::FindEntryError(existing_entry_location.clone()))?
292 .borrow()
293 .duplicate();
294 if current_group_path.last() != destination_entry_location.last() && !is_in_deleted_group {
297 let source_location_changed_time =
298 match with_node::<Entry, _, _>(other_entry, |e| e.get_times().get_location_changed()).unwrap() {
299 Some(t) => t,
300 None => {
301 log.warnings
302 .push(format!("Entry {other_entry_uuid} did not have a location updated timestamp"));
303 Times::epoch()
304 }
305 };
306 let destination_location_changed =
307 match with_node::<Entry, _, _>(&existing_entry, |e| e.get_times().get_location_changed()).unwrap() {
308 Some(t) => t,
309 None => {
310 log.warnings
311 .push(format!("Entry {other_entry_uuid} did not have a location updated timestamp"));
312 Times::now()
313 }
314 };
315 if source_location_changed_time > destination_location_changed {
316 log.events.push(MergeEvent {
317 event_type: MergeEventType::EntryLocationUpdated,
318 node_uuid: other_entry_uuid,
319 });
320 self.relocate_node(
321 other_entry_uuid,
322 &destination_entry_location,
323 current_group_path,
324 source_location_changed_time,
325 )?;
326 existing_entry_location = current_group_path.to_owned();
329 existing_entry_location.push(other_entry_uuid);
330 with_node_mut::<Entry, _, _>(&existing_entry, |e| {
331 e.get_times_mut().set_location_changed(Some(source_location_changed_time));
332 });
333 }
334 }
335 if !has_diverged_from(&existing_entry, other_entry) {
336 continue;
337 }
338 let (merged_entry, entry_merge_log) = Entry::merge(&existing_entry, other_entry)?;
341 let merged_entry = match merged_entry {
342 Some(m) => m,
343 None => continue,
344 };
345 if node_is_equals_to(&existing_entry, &merged_entry) {
346 continue;
347 }
348 let existing_entry =
349 Group::find_entry(&self.root, &existing_entry_location).ok_or(MergeError::FindEntryError(existing_entry_location))?;
350 with_node_mut::<Entry, _, _>(&existing_entry, |e| e.replaced_with(&merged_entry)).unwrap();
352 log.events.push(MergeEvent {
353 event_type: MergeEventType::EntryUpdated,
354 node_uuid: merged_entry.borrow().get_uuid(),
355 });
356 log.append(&entry_merge_log);
357 continue;
358 }
359 if self.deleted_objects.contains_key(&other_entry_uuid) {
360 continue;
361 }
362 if is_in_deleted_group {
364 continue;
365 }
366 let new_entry = other_entry.borrow().duplicate();
369 let new_entry_parent_group =
370 Group::find_group(&self.root, current_group_path).ok_or(MergeError::FindGroupError(current_group_path.to_owned()))?;
371
372 group_add_child(&new_entry_parent_group, new_entry.clone(), 0).unwrap();
374 log.events.push(MergeEvent {
376 event_type: MergeEventType::EntryCreated,
377 node_uuid: new_entry.borrow().get_uuid(),
378 });
379 }
380 for other_group in ¤t_group.borrow().downcast_ref::<Group>().unwrap().groups() {
381 let mut new_group_location = current_group_path.to_owned();
382 let other_group_uuid = other_group.borrow().get_uuid();
383 new_group_location.push(other_group_uuid);
384 if self.deleted_objects.contains_key(&other_group_uuid) || is_in_deleted_group {
385 let new_merge_log = self.merge_group(&new_group_location, other_group, true)?;
386 log.append(&new_merge_log);
387 continue;
388 }
389 let destination_group_location = Self::find_node_location(&self.root, other_group_uuid);
390 if let Some(destination_group_location) = &destination_group_location {
392 if current_group_path != destination_group_location {
393 let mut existing_group_location = destination_group_location.clone();
394 existing_group_location.push(other_group_uuid);
395 let existing_group = Group::find_group(&self.root, &existing_group_location)
398 .ok_or(MergeError::FindGroupError(existing_group_location))?;
399 let existing_group_location_changed =
400 match with_node::<Group, _, _>(&existing_group, |g| g.get_times().get_location_changed()).unwrap() {
401 Some(t) => t,
402 None => {
403 let uuid = existing_group.borrow().get_uuid();
404 log.warnings.push(format!("Entry {uuid} did not have a location changed timestamp"));
405 Times::now()
406 }
407 };
408 let other_group_location_changed =
409 match with_node::<Group, _, _>(other_group, |g| g.get_times().get_location_changed()).unwrap() {
410 Some(t) => t,
411 None => {
412 log.warnings
413 .push(format!("Entry {other_group_uuid} did not have a location changed timestamp"));
414 Times::epoch()
415 }
416 };
417 if existing_group_location_changed < other_group_location_changed {
419 self.relocate_node(
420 other_group_uuid,
421 destination_group_location,
422 current_group_path,
423 other_group_location_changed,
424 )?;
425 log.events.push(MergeEvent {
426 event_type: MergeEventType::GroupLocationUpdated,
427 node_uuid: other_group_uuid,
428 });
429 let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
430 log.append(&new_merge_log);
431 continue;
432 }
433 }
434 let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
437 log.append(&new_merge_log);
438 continue;
439 }
440 let new_group = other_group.borrow().duplicate();
443 with_node_mut::<Group, _, _>(&new_group, |g| g.reset_children(vec![])).unwrap();
445 log.events.push(MergeEvent {
446 event_type: MergeEventType::GroupCreated,
447 node_uuid: new_group.borrow().get_uuid(),
448 });
449 let new_group_parent_group =
450 Group::find_group(&self.root, current_group_path).ok_or(MergeError::FindGroupError(current_group_path.to_owned()))?;
451 with_node_mut::<Group, _, _>(&new_group_parent_group, |g| g.add_child(new_group, 0)).unwrap();
452 let new_merge_log = self.merge_group(&new_group_location, other_group, is_in_deleted_group)?;
453 log.append(&new_merge_log);
454 }
455 Ok(log)
456 }
457
458 fn relocate_node(
459 &self,
460 node_uuid: Uuid,
461 from: &[Uuid],
462 to: &[Uuid],
463 new_location_changed_timestamp: NaiveDateTime,
464 ) -> Result<(), MergeError> {
465 let source_group = Group::find_group(&self.root, from).ok_or(MergeError::FindGroupError(from.to_owned()))?;
466 let relocated_node = with_node_mut::<Group, _, _>(&source_group, |s| s.remove_node(node_uuid)).unwrap()?;
467 relocated_node
468 .borrow_mut()
469 .get_times_mut()
470 .set_location_changed(Some(new_location_changed_timestamp));
471
472 let destination_group = Group::find_group(&self.root, to).ok_or(MergeError::FindGroupError(to.to_owned()))?;
473 group_add_child(&destination_group, relocated_node, 0).unwrap();
474 Ok(())
475 }
476}
477
478pub(crate) fn has_diverged_from(node: &NodePtr, other_node: &NodePtr) -> bool {
479 if let Some(entry) = node.borrow().downcast_ref::<Entry>()
480 && let Some(other_entry) = other_node.borrow().downcast_ref::<Entry>()
481 {
482 return entry._has_diverged_from(other_entry);
483 }
484 if let Some(group) = node.borrow().downcast_ref::<Group>()
485 && let Some(other_group) = other_node.borrow().downcast_ref::<Group>()
486 {
487 return group._has_diverged_from(other_group);
488 }
489 false
490}
491
492impl Group {
493 pub(crate) fn find_group(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
494 let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
495 let node_ref = Self::get_by_uuid(group, &path)?;
496 if node_is_group(&node_ref) { Some(node_ref) } else { None }
497 }
498
499 pub(crate) fn find_entry(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
500 let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
501 let node_ref = Self::get_by_uuid(group, &path)?;
502 if node_is_entry(&node_ref) { Some(node_ref) } else { None }
503 }
504
505 pub(crate) fn remove_node(&mut self, uuid: Uuid) -> Result<NodePtr, MergeError> {
506 let mut removed_node = None;
507 self.children.retain(|c| {
508 if c.borrow().get_uuid() == uuid {
509 removed_node = Some(NodePtr::from(c));
510 return false;
511 }
512 true
513 });
514
515 let title = self.get_title().unwrap_or("No title").to_string();
516 let node = removed_node.ok_or(MergeError::GenericError(format!("Could not find node {uuid} in group \"{title}\"")))?;
517 Ok(node)
518 }
519
520 pub(crate) fn find_node_location(parent: &NodePtr, id: Uuid) -> Option<Vec<Uuid>> {
521 let parent_uuid = parent.borrow().get_uuid();
522 let mut current_location = vec![parent_uuid];
523 for node in &group_get_children(parent).unwrap_or_default() {
524 let node_uuid = node.borrow().get_uuid();
525 if node_is_entry(node) {
526 if node_uuid == id {
527 return Some(current_location);
529 }
530 } else if node_is_group(node) {
531 if node_uuid == id {
532 return Some(current_location);
534 }
535 if let Some(mut location) = Self::find_node_location(node, id) {
536 current_location.append(&mut location);
537 return Some(current_location);
538 }
539 }
540 }
541 None
542 }
543
544 pub(crate) fn merge_with(group: &NodePtr, other: &NodePtr) -> Result<MergeLog, MergeError> {
545 let mut log = MergeLog::default();
546
547 let group_uuid = group.borrow().get_uuid();
548
549 let other = other.borrow();
550 let other = other
551 .downcast_ref::<Group>()
552 .ok_or(MergeError::GenericError("Could not downcast node to group".to_string()))?;
553 let source_last_modification = match other.times.get_last_modification() {
554 Some(t) => t,
555 None => {
556 log.warnings
557 .push(format!("Group {group_uuid} did not have a last modification timestamp"));
558 Times::epoch()
559 }
560 };
561 let destination_last_modification = match group.borrow().get_times().get_last_modification() {
562 Some(t) => t,
563 None => {
564 log.warnings
565 .push(format!("Group {group_uuid} did not have a last modification timestamp"));
566 Times::now()
567 }
568 };
569 if destination_last_modification == source_last_modification {
570 if group.borrow().downcast_ref::<Group>().unwrap()._has_diverged_from(other) {
571 return Err(MergeError::GroupModificationTimeNotUpdated(other.uuid.to_string()));
575 }
576 return Ok(log);
577 }
578 if destination_last_modification > source_last_modification {
579 return Ok(log);
580 }
581 with_node_mut::<Group, _, _>(group, |group| {
582 group.name = other.name.clone();
583 group.notes = other.notes.clone();
584 group.icon_id = other.icon_id;
585 group.custom_icon_uuid = other.custom_icon_uuid;
586 group.custom_data = other.custom_data.clone();
587 let current_times = group.times.clone();
589 group.times = other.times.clone();
590 if let Some(t) = current_times.get_location_changed() {
591 group.times.set_location_changed(Some(t));
592 }
593 group.is_expanded = other.is_expanded;
594 group.default_autotype_sequence = other.default_autotype_sequence.clone();
595 group.enable_autotype = other.enable_autotype;
596 group.enable_searching = other.enable_searching;
597 group.last_top_visible_entry = other.last_top_visible_entry;
598 })
599 .unwrap();
600 log.events.push(MergeEvent {
601 event_type: MergeEventType::GroupUpdated,
602 node_uuid: group_uuid,
603 });
604 Ok(log)
605 }
606
607 pub(crate) fn _has_diverged_from(&self, other: &Group) -> bool {
608 let new_times = Times::new();
609 let mut self_purged = self.clone();
610 self_purged.times = new_times.clone();
611 self_purged.children = vec![];
612 let mut other_purged = other.clone();
613 other_purged.times = new_times.clone();
614 other_purged.children = vec![];
615 !self_purged.eq(&other_purged)
616 }
617
618 fn replace_entry(root: &NodePtr, entry: &NodePtr) -> bool {
619 let uuid = entry.borrow().get_uuid();
620 if let Some(target_entry) = search_node_by_uuid_with_specific_type::<Entry>(root, uuid) {
621 return with_node_mut::<Entry, _, _>(&target_entry, |e| e.replaced_with(entry)).unwrap_or(false);
622 }
623 false
624 }
625
626 pub(crate) fn has_group(&self, uuid: Uuid) -> bool {
627 self.children.iter().any(|n| n.borrow().get_uuid() == uuid && node_is_group(n))
628 }
629
630 fn get_or_create_group(group: &NodePtr, location: &[Uuid], create_groups: bool) -> crate::Result<NodePtr> {
631 if location.is_empty() {
632 return Err("Empty location.".into());
633 }
634
635 let mut remaining_location = location.to_owned();
636 remaining_location.remove(0);
637
638 if remaining_location.is_empty() {
639 return Ok(group.clone());
640 }
641
642 let next_location = &remaining_location[0];
643 let mut next_location_uuid = *next_location;
644
645 if !with_node::<Group, _, _>(group, |g| g.has_group(next_location_uuid)).unwrap() && create_groups {
646 let mut current_group: Option<NodePtr> = None;
647 for i in (0..(remaining_location.len())).rev() {
648 let mut new_group = Group::new(&remaining_location[i].to_string());
649 new_group.set_uuid(remaining_location[i]);
650 if let Some(current_group) = current_group {
651 let count = group_get_children(group).map(|c| c.len()).unwrap_or(0);
652 new_group.add_child(current_group, count);
653 }
654 current_group = Some(rc_refcell_node(new_group));
655 }
656
657 if let Some(current_group) = current_group {
658 next_location_uuid = current_group.borrow().get_uuid();
659 let count = group_get_children(group).map_or(0, |c| c.len());
660 group_add_child(group, current_group, count)?;
661 } else {
662 return Err("Could not create group.".into());
663 }
664 }
665
666 let mut target = None;
667 for node in group_get_children(group).unwrap_or_default().iter() {
668 if node_is_group(node) && node.borrow().get_uuid() == next_location_uuid {
669 target = Some(node.clone());
670 break;
671 }
672 }
673
674 match &target {
675 Some(target) => Self::get_or_create_group(target, &remaining_location, create_groups),
676 None => Err("The group was not found.".into()),
677 }
678 }
679
680 pub(crate) fn insert_entry(group: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
681 let group = Self::get_or_create_group(group, location, true)?;
682 with_node_mut::<Group, _, _>(&group, |g| {
683 let count = g.children.len();
684 g.add_child(entry, count);
685 Ok::<(), crate::Error>(())
686 })
687 .ok_or("Could not add entry")??;
688 Ok(())
689 }
690
691 pub(crate) fn remove_entry(group: &NodePtr, uuid: Uuid, location: &[Uuid]) -> crate::Result<NodePtr> {
692 let group = Self::get_or_create_group(group, location, false)?;
693
694 let mut removed_entry: Option<NodePtr> = None;
695 let mut new_nodes: Vec<NodePtr> = vec![];
696 println!(
697 "Searching for entry {} in {}",
698 uuid,
699 group.borrow().get_title().unwrap_or("No title")
700 );
701
702 with_node::<Group, _, _>(&group, |g| {
703 for node in g.children.iter() {
704 if node_is_entry(node) {
705 let node_uuid = node.borrow().get_uuid();
706 println!("Saw entry {node_uuid}");
707 if node_uuid != uuid {
708 new_nodes.push(NodePtr::from(node));
709 continue;
710 }
711 removed_entry = Some(NodePtr::from(node));
712 } else if node_is_group(node) {
713 new_nodes.push(NodePtr::from(node));
714 }
715 }
716 });
717
718 if let Some(entry) = removed_entry {
719 with_node_mut::<Group, _, _>(&group, |g| g.reset_children(new_nodes)).ok_or("Could not reset children")?;
720 Ok(entry)
721 } else {
722 let title = group.borrow().get_title().unwrap_or("No title").to_string();
723 Err(format!("Could not find entry {uuid} in group \"{title}\".").into())
724 }
725 }
726
727 pub(crate) fn find_entry_location(&self, uuid: Uuid) -> Option<Vec<Uuid>> {
728 let mut current_location = vec![self.uuid];
729 for node in &self.children {
730 if node_is_entry(node) {
731 if node.borrow().get_uuid() == uuid {
732 return Some(current_location);
733 }
734 } else if let Some(g) = node.borrow().downcast_ref::<Group>()
735 && let Some(mut location) = g.find_entry_location(uuid)
736 {
737 current_location.append(&mut location);
738 return Some(current_location);
739 }
740 }
741 None
742 }
743
744 pub(crate) fn add_entry(parent: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
745 if location.is_empty() {
746 panic!("TODO handle this with a Response.");
747 }
748
749 let mut remaining_location = location.to_owned();
750 remaining_location.remove(0);
751
752 if remaining_location.is_empty() {
753 with_node_mut::<Group, _, _>(parent, |g| {
754 let count = g.children.len();
755 g.add_child(entry, count);
756 Ok::<(), crate::Error>(())
757 })
758 .ok_or("Could not add entry")??;
759 return Ok(());
760 }
761
762 let next_location = remaining_location[0];
763
764 println!("Searching for group {next_location:?}");
765 for node in group_get_children(parent).unwrap_or_default() {
766 if node_is_group(&node) {
767 if node.borrow().get_uuid() != next_location {
768 continue;
769 }
770 Self::add_entry(&node, entry, &remaining_location)?;
771 return Ok(());
772 }
773 }
774
775 let new_group = rc_refcell_node(Group::new(&next_location.to_string()));
777 new_group.borrow_mut().set_uuid(next_location);
778 Self::add_entry(&new_group, entry, &remaining_location)?;
779 let count = group_get_children(parent).map_or(0, |c| c.len());
780 group_add_child(parent, new_group, count)?;
781 Ok(())
782 }
783
784 #[allow(clippy::too_many_lines)]
786 pub fn merge(root: &NodePtr, other_group: &NodePtr) -> crate::Result<MergeLog> {
787 let mut log = MergeLog::default();
788
789 let other_entries = with_node::<Group, _, _>(other_group, |g| Ok(g.get_all_entries(&[])))
790 .unwrap_or(Err(crate::Error::from("Could not downcast other group to group")))?;
791
792 for (entry, entry_location) in &other_entries {
794 let entry_uuid = entry.borrow().get_uuid();
795 let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
796
797 let existing_entry = match the_entry {
798 Some(e) => e,
799 None => continue,
800 };
801
802 let the_entry_location = with_node::<Group, _, _>(root, |g| Ok(g.find_entry_location(entry_uuid)))
803 .unwrap_or(Err("Could not downcast root to group"))?;
804
805 let existing_entry_location = match the_entry_location {
806 Some(l) => l,
807 None => continue,
808 };
809
810 let source_location_changed_time = if let Some(t) = entry.borrow().get_times().get_location_changed() {
811 t
812 } else {
813 log.warnings
814 .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
815 Times::epoch()
816 };
817 let destination_location_changed = if let Some(t) = existing_entry.borrow().get_times().get_location_changed() {
818 t
819 } else {
820 log.warnings
821 .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
822 Times::now()
823 };
824 if source_location_changed_time > destination_location_changed {
825 log.events.push(MergeEvent {
826 event_type: MergeEventType::EntryLocationUpdated,
827 node_uuid: entry_uuid,
828 });
829 Self::remove_entry(root, entry_uuid, &existing_entry_location)?;
830 Self::insert_entry(root, entry.borrow().duplicate(), entry_location)?;
831 }
832 }
833
834 for (entry, entry_location) in &other_entries {
836 let entry_uuid = entry.borrow().get_uuid();
837 let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
838 if let Some(existing_entry) = the_entry {
839 if node_is_equals_to(&existing_entry, entry) {
840 continue;
841 }
842
843 let source_last_modification = if let Some(t) = entry.borrow().get_times().get_last_modification() {
844 t
845 } else {
846 log.warnings
847 .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
848 Times::epoch()
849 };
850 let destination_last_modification = if let Some(t) = existing_entry.borrow().get_times().get_last_modification() {
851 t
852 } else {
853 log.warnings
854 .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
855 Times::now()
856 };
857
858 if destination_last_modification == source_last_modification {
859 if !node_is_equals_to(&existing_entry, entry) {
860 return Err("Entries have the same modification time but are not the same!".into());
864 }
865 continue;
866 }
867
868 let (merged_entry, entry_merge_log) = if destination_last_modification > source_last_modification {
869 Entry::merge(&existing_entry, entry)?
870 } else {
871 Entry::merge(entry, &existing_entry)?
872 };
873 let Some(merged_entry) = merged_entry else {
874 continue;
875 };
876 if node_is_equals_to(&existing_entry, &merged_entry) {
878 continue;
879 }
880
881 Group::replace_entry(root, &merged_entry);
882
883 log.events.push(MergeEvent {
884 event_type: MergeEventType::EntryUpdated,
885 node_uuid: merged_entry.borrow().get_uuid(),
886 });
887 log = log.merge_with(&entry_merge_log);
888 } else {
889 Self::add_entry(root, entry.borrow().duplicate(), entry_location)?;
890 log.events.push(MergeEvent {
892 event_type: MergeEventType::EntryCreated,
893 node_uuid: entry.borrow().get_uuid(),
894 });
895 }
896 }
897
898 Ok(log)
900 }
901
902 pub(crate) fn get_all_entries(&self, current_location: &[Uuid]) -> Vec<(NodePtr, Vec<Uuid>)> {
905 let mut response: Vec<(NodePtr, Vec<Uuid>)> = vec![];
906 let mut new_location = current_location.to_owned();
907 new_location.push(self.uuid);
908
909 for node in &self.children {
910 if node_is_entry(node) {
911 response.push((node.into(), new_location.clone()));
912 }
913 with_node::<Group, _, _>(node, |g| {
914 let mut new_entries = g.get_all_entries(&new_location);
915 response.append(&mut new_entries);
916 });
917 }
918 response
919 }
920}
921
922#[cfg(test)]
923pub fn entry_set_field_and_commit(entry: &NodePtr, field_name: &str, field_value: &str) -> crate::Result<()> {
924 with_node_mut::<Entry, _, _>(entry, |entry| {
925 entry.set_field_and_commit(field_name, field_value);
926 Ok(())
927 })
928 .unwrap_or(Err("node is not an Entry.".to_string()))?;
929 Ok(())
930}
931
932impl Entry {
933 pub(crate) fn merge(entry: &NodePtr, other: &NodePtr) -> Result<(Option<NodePtr>, MergeLog), MergeError> {
934 let mut log = MergeLog::default();
935 let source_last_modification = match with_node::<Entry, _, _>(other, |e| e.get_times().get_last_modification()).unwrap() {
936 Some(t) => t,
937 None => {
938 let info = format!("Entry {} did not have a last modification timestamp", other.borrow().get_uuid());
939 log.warnings.push(info);
940 Times::epoch()
941 }
942 };
943
944 let destination_last_modification = match with_node::<Entry, _, _>(entry, |e| e.get_times().get_last_modification()).unwrap() {
945 Some(t) => t,
946 None => {
947 let info = format!("Entry {} did not have a last modification timestamp", entry.borrow().get_uuid());
948 log.warnings.push(info);
949 Times::epoch()
950 }
951 };
952
953 if destination_last_modification == source_last_modification {
954 if !crate::db::merge::has_diverged_from(entry, other) {
955 return Err(MergeError::EntryModificationTimeNotUpdated(other.borrow().get_uuid().to_string()));
957 }
958 return Ok((None, log));
959 }
960 let (mut merged_entry, entry_merge_log) = with_node::<Entry, _, _>(entry, |entry| {
961 with_node::<Entry, _, _>(other, |other| {
962 if destination_last_modification > source_last_modification {
963 entry.merge_history(other)
964 } else {
965 other.merge_history(entry)
966 }
967 })
968 .unwrap()
969 })
970 .unwrap()?;
971
972 if let location_changed_timestamp @ Some(_) = entry.borrow().get_times().get_location_changed() {
973 merged_entry.get_times_mut().set_location_changed(location_changed_timestamp);
974 }
975 Ok((Some(rc_refcell_node(merged_entry)), entry_merge_log))
976 }
977
978 pub(crate) fn merge_history(&self, other: &Entry) -> Result<(Entry, MergeLog), MergeError> {
979 let mut log = MergeLog::default();
980 let mut source_history = match &other.history {
981 Some(h) => h.clone(),
982 None => {
983 log.warnings
984 .push(format!("Entry {} from source database had no history.", self.uuid));
985 History::default()
986 }
987 };
988 let mut destination_history = match &self.history {
989 Some(h) => h.clone(),
990 None => {
991 log.warnings
992 .push(format!("Entry {} from destination database had no history.", self.uuid));
993 History::default()
994 }
995 };
996 let mut response = self.clone();
997 if other.has_uncommited_changes() {
998 log.warnings
999 .push(format!("Entry {} from source database has uncommitted changes.", self.uuid));
1000 source_history.add_entry(other.clone());
1001 }
1002 let history_merge_log = destination_history.merge_with(&source_history)?;
1005 response.history = Some(destination_history);
1006 Ok((response, log.merge_with(&history_merge_log)))
1007 }
1008
1009 pub(crate) fn _has_diverged_from(&self, other_entry: &Entry) -> bool {
1011 let new_times = Times::default();
1012
1013 let mut self_without_times = self.clone();
1014 self_without_times.times = new_times.clone();
1015
1016 let mut other_without_times = other_entry.clone();
1017 other_without_times.times = new_times;
1018 !self_without_times.eq(&other_without_times)
1019 }
1020
1021 #[cfg(test)]
1056 pub(crate) fn set_field_and_commit(&mut self, field_name: &str, field_value: &str) {
1057 self.set_unprotected_field_pair(field_name, Some(field_value));
1058 std::thread::sleep(std::time::Duration::from_secs(1));
1059 self.update_history();
1060 }
1061
1062 pub(crate) fn replaced_with(&mut self, other: &NodePtr) -> bool {
1063 let mut success = false;
1064 with_node::<Entry, _, _>(other, |other| {
1065 self.uuid = other.uuid;
1066 self.fields = other.fields.clone();
1067 self.autotype = other.autotype.clone();
1068 self.tags = other.tags.clone();
1069 self.times = other.times.clone();
1070 self.custom_data = other.custom_data.clone();
1071 self.icon_id = other.icon_id;
1072 self.custom_icon = other.custom_icon;
1073 self.foreground_color = other.foreground_color;
1074 self.background_color = other.background_color;
1075 self.override_url = other.override_url.clone();
1076 self.quality_check = other.quality_check;
1077 self.history = other.history.clone();
1078 success = true;
1080 });
1081 success
1082 }
1083}
1084
1085impl History {
1086 #[cfg(test)]
1089 pub(crate) fn is_ordered(&self) -> bool {
1090 let mut last_modification_time: Option<chrono::NaiveDateTime> = None;
1091 for entry in &self.entries {
1092 if last_modification_time.is_none() {
1093 last_modification_time = entry.times.get_last_modification();
1094 }
1095
1096 let entry_modification_time = entry.times.get_last_modification().unwrap();
1097 if last_modification_time.unwrap() < entry_modification_time {
1099 return false;
1100 }
1101 last_modification_time = Some(entry_modification_time);
1102 }
1103 true
1104 }
1105
1106 pub(crate) fn merge_with(&mut self, other: &History) -> Result<MergeLog, MergeError> {
1108 let mut log = MergeLog::default();
1109 let mut new_history_entries: HashMap<chrono::NaiveDateTime, Entry> = HashMap::new();
1110
1111 for history_entry in &self.entries {
1112 let modification_time = history_entry.times.get_last_modification().unwrap();
1113 if new_history_entries.contains_key(&modification_time) {
1114 return Err(MergeError::DuplicateHistoryEntries(
1115 modification_time.to_string(),
1116 history_entry.uuid.to_string(),
1117 ));
1118 }
1119 new_history_entries.insert(modification_time, history_entry.clone());
1120 }
1121
1122 for history_entry in &other.entries {
1123 let modification_time = history_entry.times.get_last_modification().unwrap();
1124 let existing_history_entry = new_history_entries.get(&modification_time);
1125 if let Some(existing_history_entry) = existing_history_entry {
1126 if existing_history_entry._has_diverged_from(history_entry) {
1127 log.warnings.push(format!(
1128 "History entries for {} have the same modification timestamp but were not the same.",
1129 existing_history_entry.uuid
1130 ));
1131 }
1132 } else {
1133 new_history_entries.insert(modification_time, history_entry.clone());
1134 }
1135 }
1136
1137 let mut all_modification_times: Vec<&chrono::NaiveDateTime> = new_history_entries.keys().collect();
1138 all_modification_times.sort();
1139 all_modification_times.reverse();
1140 let mut new_entries: Vec<Entry> = vec![];
1141 for modification_time in &all_modification_times {
1142 new_entries.push(new_history_entries.get(modification_time).unwrap().clone());
1143 }
1144 self.entries = new_entries;
1145 Ok(log)
1146 }
1147}
1148
1149#[cfg(test)]
1150mod merge_tests {
1151 use std::{thread, time};
1152 use uuid::Uuid;
1153
1154 use crate::db::{
1155 CustomIcon, Database, Entry, Group, Node, NodePtr, Times, group_add_child, group_get_children, node_is_group, rc_refcell_node,
1156 search_node_by_uuid_with_specific_type, with_node, with_node_mut,
1157 };
1158
1159 fn get_entry(db: &Database, path: &[&str]) -> NodePtr {
1160 Group::get(&db.root, path).unwrap()
1161 }
1162
1163 fn get_group(db: &Database, path: &[&str]) -> NodePtr {
1164 Group::get(&db.root, path).unwrap()
1165 }
1166
1167 fn get_all_groups(group: &NodePtr) -> Vec<NodePtr> {
1168 let mut response: Vec<NodePtr> = vec![];
1169 for node in group_get_children(group).unwrap() {
1170 if node_is_group(&node) {
1171 let mut new_groups = get_all_groups(&node);
1172 response.append(&mut new_groups);
1173 response.push(node);
1174 }
1175 }
1176
1177 response
1178 }
1179
1180 fn get_all_entries(group: &NodePtr) -> Vec<NodePtr> {
1181 let mut response: Vec<NodePtr> = vec![];
1182 for node in group_get_children(group).unwrap() {
1183 if node_is_group(&node) {
1184 let mut new_entries = get_all_entries(&node);
1185 response.append(&mut new_entries);
1186 } else {
1187 response.push(node);
1188 }
1189 }
1190 response
1191 }
1192
1193 const ROOT_GROUP_ID: &str = "00000000-0000-0000-0000-000000000001";
1194 const GROUP1_ID: &str = "00000000-0000-0000-0000-000000000002";
1195 const GROUP2_ID: &str = "00000000-0000-0000-0000-000000000003";
1196 const SUBGROUP1_ID: &str = "00000000-0000-0000-0000-000000000004";
1197 const SUBGROUP2_ID: &str = "00000000-0000-0000-0000-000000000005";
1198
1199 const ENTRY1_ID: &str = "00000000-0000-0000-0000-000000000006";
1200 const ENTRY2_ID: &str = "00000000-0000-0000-0000-000000000007";
1201
1202 fn create_test_database() -> Database {
1203 let mut db = Database::new(Default::default());
1204 let mut root_group = Group::new("root");
1205 root_group.uuid = Uuid::parse_str(ROOT_GROUP_ID).unwrap();
1206
1207 let mut group1 = Group::new("group1");
1208 group1.uuid = Uuid::parse_str(GROUP1_ID).unwrap();
1209 let mut group2 = Group::new("group2");
1210 group2.uuid = Uuid::parse_str(GROUP2_ID).unwrap();
1211
1212 let mut subgroup1 = Group::new("subgroup1");
1213 subgroup1.uuid = Uuid::parse_str(SUBGROUP1_ID).unwrap();
1214 let mut subgroup2 = Group::new("subgroup2");
1215 subgroup2.uuid = Uuid::parse_str(SUBGROUP2_ID).unwrap();
1216
1217 let mut entry1 = Entry::default();
1219 entry1.set_uuid(Uuid::parse_str(ENTRY1_ID).unwrap());
1220 entry1.set_field_and_commit("Title", "entry1");
1221 root_group.add_child(rc_refcell_node(entry1), 0);
1222
1223 let mut entry2 = Entry::default();
1225 entry2.set_uuid(Uuid::parse_str(ENTRY2_ID).unwrap());
1226 entry2.set_field_and_commit("Title", "entry2");
1227 subgroup1.add_child(rc_refcell_node(entry2), 0);
1228
1229 group1.add_child(rc_refcell_node(subgroup1), 0);
1230 group2.add_child(rc_refcell_node(subgroup2), 0);
1231
1232 root_group.add_child(rc_refcell_node(group1), 1);
1233 root_group.add_child(rc_refcell_node(group2), 2);
1234
1235 db.root = rc_refcell_node(root_group).into();
1236 db
1237 }
1238
1239 #[test]
1240 fn test_idempotence() {
1241 let mut destination_db = create_test_database();
1242 let source_db = destination_db.clone();
1243
1244 let entry_count_before = get_all_entries(&destination_db.root).len();
1245 let group_count_before = get_all_groups(&destination_db.root).len();
1246
1247 let merge_result = destination_db.merge(&source_db).unwrap();
1248 assert_eq!(merge_result.warnings.len(), 0);
1249 assert_eq!(merge_result.events.len(), 0);
1250 assert_eq!(group_get_children(&destination_db.root).unwrap().len(), 3);
1251
1252 assert_eq!(destination_db, source_db);
1255
1256 let entry_count_after = get_all_entries(&destination_db.root).len();
1257 let group_count_after = get_all_groups(&destination_db.root).len();
1258 assert_eq!(entry_count_after, entry_count_before);
1259 assert_eq!(group_count_after, group_count_before);
1260
1261 let entry = get_all_entries(&destination_db.root)[0].clone();
1262 with_node_mut::<Entry, _, _>(&entry, |entry| {
1263 entry.set_field_and_commit("Title", "entry1_updated");
1264 });
1265
1266 let merge_result = destination_db.merge(&source_db).unwrap();
1267 assert_eq!(merge_result.warnings.len(), 0);
1268 assert_eq!(merge_result.events.len(), 0);
1269 let destination_db_just_after_merge = destination_db.clone();
1270
1271 let merge_result = destination_db.merge(&source_db).unwrap();
1272 assert_eq!(merge_result.warnings.len(), 0);
1273 assert_eq!(merge_result.events.len(), 0);
1274 assert_eq!(destination_db_just_after_merge, destination_db);
1277 }
1278
1279 #[test]
1280 fn test_add_new_entry() {
1281 let mut destination_db = create_test_database();
1282 let source_db = destination_db.clone();
1283
1284 let entry_count_before = get_all_entries(&destination_db.root).len();
1285 let group_count_before = get_all_groups(&destination_db.root).len();
1286
1287 let mut new_entry = Entry::default();
1288 new_entry.set_field_and_commit("Title", "new_entry");
1289 group_add_child(&source_db.root, rc_refcell_node(new_entry), 0).unwrap();
1290
1291 let merge_result = destination_db.merge(&source_db).unwrap();
1292 assert_eq!(merge_result.warnings.len(), 0);
1293 assert_eq!(merge_result.events.len(), 1);
1294
1295 let entry_count_after = get_all_entries(&destination_db.root).len();
1296 let group_count_after = get_all_groups(&destination_db.root).len();
1297 assert_eq!(entry_count_after, entry_count_before + 1);
1298 assert_eq!(group_count_after, group_count_before);
1299
1300 let root_entries = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap();
1301 assert_eq!(root_entries.len(), 2);
1302
1303 let new_entry = get_entry(&destination_db, &["new_entry"]);
1304 assert_eq!(new_entry.borrow().get_title().unwrap(), "new_entry".to_string());
1305
1306 let merge_result = destination_db.merge(&source_db).unwrap();
1308 assert_eq!(merge_result.warnings.len(), 0);
1309 assert_eq!(merge_result.events.len(), 0);
1310
1311 let entry_count_after = get_all_entries(&destination_db.root).len();
1312 let group_count_after = get_all_groups(&destination_db.root).len();
1313 assert_eq!(entry_count_after, entry_count_before + 1);
1314 assert_eq!(group_count_after, group_count_before);
1315 }
1316
1317 #[test]
1318 fn test_deleted_entry_in_destination() {
1319 let mut destination_db = create_test_database();
1320 let source_db = destination_db.clone();
1321
1322 let entry_count_before = get_all_entries(&destination_db.root).len();
1323 let group_count_before = get_all_groups(&destination_db.root).len();
1324
1325 let mut deleted_entry = Entry::default();
1326 let deleted_entry_uuid = deleted_entry.uuid;
1327 deleted_entry.set_field_and_commit("Title", "deleted_entry");
1328 group_add_child(&source_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1329
1330 destination_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1331
1332 let merge_result = destination_db.merge(&source_db).unwrap();
1333 assert_eq!(merge_result.warnings.len(), 0);
1334 assert_eq!(merge_result.events.len(), 0);
1335
1336 let entry_count_after = get_all_entries(&destination_db.root).len();
1337 let group_count_after = get_all_groups(&destination_db.root).len();
1338 assert_eq!(entry_count_after, entry_count_before);
1339 assert_eq!(group_count_after, group_count_before);
1340
1341 let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1342 assert!(new_entry.is_none());
1343 }
1344
1345 #[test]
1346 fn test_updated_entry_under_deleted_group() {
1347 let mut destination_db = create_test_database();
1348 let source_db = destination_db.clone();
1349
1350 let mut modified_entry = Entry::default();
1351 modified_entry.set_field_and_commit("Title", "original_title");
1352 group_add_child(&destination_db.root, modified_entry.duplicate(), 0).unwrap();
1353
1354 let mut deleted_group = Group::new("deleted_group");
1355 let deleted_group_uuid = deleted_group.uuid;
1356 let modified_entry_uuid = modified_entry.uuid;
1357 modified_entry.set_field_and_commit("Title", "modified_title");
1358 deleted_group.add_child(rc_refcell_node(modified_entry), 0);
1359 group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1360
1361 let entry_count_before = get_all_entries(&destination_db.root).len();
1362 let group_count_before = get_all_groups(&destination_db.root).len();
1363
1364 destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1365
1366 let merge_result = destination_db.merge(&source_db).unwrap();
1367 assert_eq!(merge_result.warnings.len(), 0);
1368 assert_eq!(merge_result.events.len(), 1);
1369
1370 let entry_count_after = get_all_entries(&destination_db.root).len();
1371 let group_count_after = get_all_groups(&destination_db.root).len();
1372 assert_eq!(entry_count_after, entry_count_before);
1373 assert_eq!(group_count_after, group_count_before);
1374
1375 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1376 assert!(deleted_group.is_none());
1377
1378 let modified_entry_location = Group::find_node_location(&destination_db.root, modified_entry_uuid);
1379 assert!(modified_entry_location.is_some());
1380
1381 let modified_entry = Group::find_entry(&destination_db.root, &[modified_entry_uuid]).unwrap();
1382 assert_eq!(modified_entry.borrow().get_title(), Some("modified_title"));
1383 }
1384
1385 #[test]
1386 fn test_deleted_group_in_destination() {
1387 let mut destination_db = create_test_database();
1388 let source_db = destination_db.clone();
1389
1390 let entry_count_before = get_all_entries(&destination_db.root).len();
1391 let group_count_before = get_all_groups(&destination_db.root).len();
1392
1393 let deleted_group = Group::new("deleted_group");
1394 let deleted_group_uuid = deleted_group.uuid;
1395 group_add_child(&source_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1396
1397 destination_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1398
1399 let merge_result = destination_db.merge(&source_db).unwrap();
1400 assert_eq!(merge_result.warnings.len(), 0);
1401 assert_eq!(merge_result.events.len(), 0);
1402
1403 let entry_count_after = get_all_entries(&destination_db.root).len();
1404 let group_count_after = get_all_groups(&destination_db.root).len();
1405 assert_eq!(entry_count_after, entry_count_before);
1406 assert_eq!(group_count_after, group_count_before);
1407
1408 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1409 assert!(deleted_group.is_none());
1410 }
1411
1412 #[test]
1413 fn test_deleted_entry_in_source() {
1414 let mut destination_db = create_test_database();
1415 let mut source_db = destination_db.clone();
1416
1417 let mut deleted_entry = Entry::default();
1418 let deleted_entry_uuid = deleted_entry.uuid;
1419 deleted_entry.set_field_and_commit("Title", "deleted_entry");
1420 group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1421
1422 let entry_count_before = get_all_entries(&destination_db.root).len();
1423 let group_count_before = get_all_groups(&destination_db.root).len();
1424
1425 thread::sleep(time::Duration::from_secs(1));
1426 source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1427
1428 let merge_result = destination_db.merge(&source_db).unwrap();
1429 assert_eq!(merge_result.warnings.len(), 0);
1430 assert_eq!(merge_result.events.len(), 1);
1431
1432 let entry_count_after = get_all_entries(&destination_db.root).len();
1433 let group_count_after = get_all_groups(&destination_db.root).len();
1434 assert_eq!(entry_count_after, entry_count_before - 1);
1435 assert_eq!(group_count_after, group_count_before);
1436
1437 let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1438 assert!(new_entry.is_none());
1439
1440 assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1441 }
1442
1443 #[test]
1444 fn test_deleted_group_in_source() {
1445 let mut destination_db = create_test_database();
1446 let mut source_db = destination_db.clone();
1447
1448 let deleted_group = Group::new("deleted_group");
1449 let deleted_group_uuid = deleted_group.uuid;
1450 group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1451
1452 let entry_count_before = get_all_entries(&destination_db.root).len();
1453 let group_count_before = get_all_groups(&destination_db.root).len();
1454
1455 thread::sleep(time::Duration::from_secs(1));
1456 source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1457
1458 let merge_result = destination_db.merge(&source_db).unwrap();
1459 assert_eq!(merge_result.warnings.len(), 0);
1460 assert_eq!(merge_result.events.len(), 1);
1461
1462 let entry_count_after = get_all_entries(&destination_db.root).len();
1463 let group_count_after = get_all_groups(&destination_db.root).len();
1464 assert_eq!(entry_count_after, entry_count_before);
1465 assert_eq!(group_count_after, group_count_before - 1);
1466
1467 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1468 assert!(deleted_group.is_none());
1469
1470 assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1471 }
1472
1473 #[test]
1474 fn test_deleted_entry_in_source_modified_in_destination() {
1475 let mut destination_db = create_test_database();
1476 let mut source_db = destination_db.clone();
1477
1478 let deleted_entry_uuid = Uuid::new_v4();
1479
1480 thread::sleep(time::Duration::from_secs(1));
1481 source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1482
1483 thread::sleep(time::Duration::from_secs(1));
1484 let mut deleted_entry = Entry::default();
1485 deleted_entry.set_uuid(deleted_entry_uuid);
1486 deleted_entry.set_field_and_commit("Title", "deleted_entry");
1487 group_add_child(&destination_db.root, rc_refcell_node(deleted_entry), 0).unwrap();
1488
1489 let entry_count_before = get_all_entries(&destination_db.root).len();
1490 let group_count_before = get_all_groups(&destination_db.root).len();
1491
1492 let merge_result = destination_db.merge(&source_db).unwrap();
1493 assert_eq!(merge_result.warnings.len(), 0);
1494 assert_eq!(merge_result.events.len(), 0);
1495
1496 let entry_count_after = get_all_entries(&destination_db.root).len();
1497 let group_count_after = get_all_groups(&destination_db.root).len();
1498 assert_eq!(entry_count_after, entry_count_before);
1499 assert_eq!(group_count_after, group_count_before);
1500
1501 let new_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1502 assert!(new_entry.is_some());
1503
1504 assert!(!destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1505 }
1506
1507 #[test]
1508 fn test_group_subtree_deletion() {
1509 let mut destination_db = create_test_database();
1510 let mut source_db = destination_db.clone();
1511
1512 let deleted_entry_uuid = Uuid::new_v4();
1513 let deleted_group_uuid = Uuid::new_v4();
1514 let deleted_subgroup_uuid = Uuid::new_v4();
1515
1516 thread::sleep(time::Duration::from_secs(1));
1517 let mut deleted_entry = Entry::default();
1518 deleted_entry.set_uuid(deleted_entry_uuid);
1519 deleted_entry.set_field_and_commit("Title", "deleted_entry");
1520
1521 let mut deleted_subgroup = Group::new("deleted_subgroup");
1522 deleted_subgroup.uuid = deleted_subgroup_uuid;
1523 deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1524
1525 let mut deleted_group = Group::new("deleted_group");
1526 deleted_group.uuid = deleted_group_uuid;
1527 deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1528
1529 group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1530
1531 thread::sleep(time::Duration::from_secs(1));
1532 source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1533 source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1534 source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1535
1536 let entry_count_before = get_all_entries(&destination_db.root).len();
1537 let group_count_before = get_all_groups(&destination_db.root).len();
1538
1539 let merge_result = destination_db.merge(&source_db).unwrap();
1540 assert_eq!(merge_result.warnings.len(), 0);
1541 assert_eq!(merge_result.events.len(), 3);
1542
1543 let entry_count_after = get_all_entries(&destination_db.root).len();
1544 let group_count_after = get_all_groups(&destination_db.root).len();
1545 assert_eq!(entry_count_after, entry_count_before - 1);
1546 assert_eq!(group_count_after, group_count_before - 2);
1547
1548 let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1549 assert!(deleted_entry.is_none());
1550 let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1551 assert!(deleted_subgroup.is_none());
1552 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1553 assert!(deleted_group.is_none());
1554
1555 assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1556 assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1557 assert!(destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1558 }
1559
1560 #[test]
1561 fn test_group_subtree_partial_deletion() {
1562 let mut destination_db = create_test_database();
1563 let mut source_db = destination_db.clone();
1564
1565 let deleted_entry_uuid = Uuid::new_v4();
1566 let deleted_group_uuid = Uuid::new_v4();
1567 let deleted_subgroup_uuid = Uuid::new_v4();
1568
1569 thread::sleep(time::Duration::from_secs(1));
1570 let mut deleted_entry = Entry::default();
1571 deleted_entry.set_uuid(deleted_entry_uuid);
1572 deleted_entry.set_field_and_commit("Title", "deleted_entry");
1573
1574 let mut deleted_subgroup = Group::new("deleted_subgroup");
1575 deleted_subgroup.uuid = deleted_subgroup_uuid;
1576 deleted_subgroup.add_child(rc_refcell_node(deleted_entry), 0);
1577
1578 thread::sleep(time::Duration::from_secs(1));
1579 source_db.deleted_objects.insert(deleted_entry_uuid, Some(Times::now()));
1580 source_db.deleted_objects.insert(deleted_subgroup_uuid, Some(Times::now()));
1581 source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1582
1583 thread::sleep(time::Duration::from_secs(1));
1584 let mut deleted_group = Group::new("deleted_group");
1585 deleted_group.uuid = deleted_group_uuid;
1586 deleted_group.add_child(rc_refcell_node(deleted_subgroup), 0);
1587
1588 group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1589
1590 let entry_count_before = get_all_entries(&destination_db.root).len();
1591 let group_count_before = get_all_groups(&destination_db.root).len();
1592
1593 let merge_result = destination_db.merge(&source_db).unwrap();
1594 assert_eq!(merge_result.warnings.len(), 0);
1595 assert_eq!(merge_result.events.len(), 2);
1596
1597 let entry_count_after = get_all_entries(&destination_db.root).len();
1598 let group_count_after = get_all_groups(&destination_db.root).len();
1599 assert_eq!(entry_count_after, entry_count_before - 1);
1600 assert_eq!(group_count_after, group_count_before - 1);
1601
1602 let deleted_entry = Group::find_node_location(&destination_db.root, deleted_entry_uuid);
1603 assert!(deleted_entry.is_none());
1604 let deleted_subgroup = Group::find_node_location(&destination_db.root, deleted_subgroup_uuid);
1605 assert!(deleted_subgroup.is_none());
1606 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1607 assert!(deleted_group.is_some());
1608
1609 assert!(destination_db.deleted_objects.contains_key(&deleted_entry_uuid));
1610 assert!(destination_db.deleted_objects.contains_key(&deleted_subgroup_uuid));
1611 assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1612 }
1613
1614 #[test]
1615 fn test_deleted_group_in_source_modified_in_destination() {
1616 let mut destination_db = create_test_database();
1617 let mut source_db = destination_db.clone();
1618
1619 let deleted_group_uuid = Uuid::new_v4();
1620
1621 thread::sleep(time::Duration::from_secs(1));
1622 source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1623
1624 thread::sleep(time::Duration::from_secs(1));
1625 let mut deleted_group = Group::new("deleted_group");
1626 deleted_group.uuid = deleted_group_uuid;
1627 group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1628
1629 let entry_count_before = get_all_entries(&destination_db.root).len();
1630 let group_count_before = get_all_groups(&destination_db.root).len();
1631
1632 let merge_result = destination_db.merge(&source_db).unwrap();
1633 assert_eq!(merge_result.warnings.len(), 0);
1634 assert_eq!(merge_result.events.len(), 0);
1635
1636 let entry_count_after = get_all_entries(&destination_db.root).len();
1637 let group_count_after = get_all_groups(&destination_db.root).len();
1638 assert_eq!(entry_count_after, entry_count_before);
1639 assert_eq!(group_count_after, group_count_before);
1640
1641 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1642 assert!(deleted_group.is_some());
1643
1644 assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1645 }
1646
1647 #[test]
1648 fn test_deleted_group_has_new_entries() {
1649 let mut destination_db = create_test_database();
1650 let mut source_db = destination_db.clone();
1651
1652 let mut deleted_group = Group::new("deleted_group");
1653 let deleted_group_uuid = deleted_group.uuid;
1654
1655 let mut new_entry = Entry::default();
1656 let new_entry_uuid = new_entry.uuid;
1657 new_entry.set_field_and_commit("Title", "new_entry");
1658 deleted_group.add_child(rc_refcell_node(new_entry), 0);
1659 group_add_child(&destination_db.root, rc_refcell_node(deleted_group), 0).unwrap();
1660
1661 let entry_count_before = get_all_entries(&destination_db.root).len();
1662 let group_count_before = get_all_groups(&destination_db.root).len();
1663
1664 thread::sleep(time::Duration::from_secs(1));
1665 source_db.deleted_objects.insert(deleted_group_uuid, Some(Times::now()));
1666
1667 let merge_result = destination_db.merge(&source_db).unwrap();
1668 assert_eq!(merge_result.warnings.len(), 0);
1669 assert_eq!(merge_result.events.len(), 0);
1670
1671 let entry_count_after = get_all_entries(&destination_db.root).len();
1672 let group_count_after = get_all_groups(&destination_db.root).len();
1673 assert_eq!(entry_count_after, entry_count_before);
1674 assert_eq!(group_count_after, group_count_before);
1675
1676 let deleted_group = Group::find_node_location(&destination_db.root, deleted_group_uuid);
1677 assert!(deleted_group.is_some());
1678 let new_entry = Group::find_node_location(&destination_db.root, new_entry_uuid);
1679 assert!(new_entry.is_some());
1680
1681 assert!(!destination_db.deleted_objects.contains_key(&deleted_group_uuid));
1682 assert!(!destination_db.deleted_objects.contains_key(&new_entry_uuid));
1683 }
1684
1685 #[test]
1686 fn test_add_new_non_root_entry() {
1687 let mut destination_db = create_test_database();
1688 let source_db = destination_db.clone();
1689
1690 let entry_count_before = get_all_entries(&destination_db.root).len();
1691 let group_count_before = get_all_groups(&destination_db.root).len();
1692
1693 let source_sub_group = with_node::<Group, _, _>(&source_db.root, |group| group.groups()).unwrap()[0].clone();
1694
1695 let mut new_entry = Entry::default();
1696 let new_entry_uuid = new_entry.uuid;
1697 new_entry.set_field_and_commit("Title", "new_entry");
1698 group_add_child(&source_sub_group, rc_refcell_node(new_entry), 0).unwrap();
1700
1701 let merge_result = destination_db.merge(&source_db).unwrap();
1702 assert_eq!(merge_result.warnings.len(), 0);
1703 assert_eq!(merge_result.events.len(), 1);
1704
1705 let entry_count_after = get_all_entries(&destination_db.root).len();
1706 let group_count_after = get_all_groups(&destination_db.root).len();
1707 assert_eq!(entry_count_after, entry_count_before + 1);
1708 assert_eq!(group_count_after, group_count_before);
1709
1710 let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1711 assert_eq!(created_entry_location.len(), 2);
1712 }
1713
1714 #[test]
1715 fn test_add_new_entry_new_group() {
1716 let mut destination_db = create_test_database();
1717 let source_db = destination_db.clone();
1718
1719 let group_count_before = get_all_groups(&destination_db.root).len();
1720 let entry_count_before = get_all_entries(&destination_db.root).len();
1721
1722 let mut source_group = Group::new("new_group");
1723 let mut source_sub_group = Group::new("new_subgroup");
1724
1725 let mut new_entry = Entry::default();
1726 let new_entry_uuid = new_entry.uuid;
1727 new_entry.set_field_and_commit("Title", "new_entry");
1728 source_sub_group.add_child(rc_refcell_node(new_entry), 0);
1729 source_group.add_child(rc_refcell_node(source_sub_group), 0);
1730 group_add_child(&source_db.root, rc_refcell_node(source_group), 0).unwrap();
1731
1732 let merge_result = destination_db.merge(&source_db).unwrap();
1733 assert_eq!(merge_result.warnings.len(), 0);
1734 assert_eq!(merge_result.events.len(), 3);
1735
1736 let group_count_after = get_all_groups(&destination_db.root).len();
1737 let entry_count_after = get_all_entries(&destination_db.root).len();
1738 assert_eq!(entry_count_after, entry_count_before + 1);
1739 assert_eq!(group_count_after, group_count_before + 2);
1740
1741 let created_entry_location = Group::find_node_location(&destination_db.root, new_entry_uuid).unwrap();
1742 assert_eq!(created_entry_location.len(), 3);
1743 }
1744
1745 #[test]
1746 fn test_entry_relocation_existing_group() {
1747 let mut destination_db = create_test_database();
1748 let source_db = destination_db.clone();
1749
1750 let group_count_before = get_all_groups(&destination_db.root).len();
1751 let entry_count_before = get_all_entries(&destination_db.root).len();
1752
1753 thread::sleep(time::Duration::from_secs(1));
1754 let new_location_changed_timestamp = Times::now();
1755
1756 source_db
1757 .relocate_node(
1758 Uuid::parse_str(ENTRY2_ID).unwrap(),
1759 &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1760 &[Uuid::parse_str(GROUP2_ID).unwrap()],
1761 new_location_changed_timestamp,
1762 )
1763 .unwrap();
1764
1765 let merge_result = destination_db.merge(&source_db).unwrap();
1766 assert_eq!(merge_result.warnings.len(), 0);
1767 assert_eq!(merge_result.events.len(), 1);
1768
1769 let group_count_after = get_all_groups(&destination_db.root).len();
1770 let entry_count_after = get_all_entries(&destination_db.root).len();
1771 assert_eq!(group_count_after, group_count_before);
1772 assert_eq!(entry_count_after, entry_count_before);
1773
1774 let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1775 assert_eq!(moved_entry_location.len(), 2);
1776 assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1777 assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1778
1779 let moved_entry = get_entry(&destination_db, &["group2", "entry2"]);
1780 let ts = moved_entry.borrow().get_times().get_location_changed().unwrap();
1781 assert_eq!(ts, new_location_changed_timestamp);
1782 }
1783
1784 #[test]
1785 fn test_entry_relocation_and_update() {
1786 let mut destination_db = create_test_database();
1787 let source_db = destination_db.clone();
1788
1789 let group_count_before = get_all_groups(&destination_db.root).len();
1790 let entry_count_before = get_all_entries(&destination_db.root).len();
1791
1792 let entry2 = Group::find_entry(
1793 &source_db.root,
1794 &[
1795 Uuid::parse_str(GROUP1_ID).unwrap(),
1796 Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1797 Uuid::parse_str(ENTRY2_ID).unwrap(),
1798 ],
1799 )
1800 .unwrap();
1801
1802 with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1803 entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1804 });
1805
1806 thread::sleep(time::Duration::from_secs(1));
1807 let new_location_changed_timestamp = Times::now();
1808
1809 source_db
1810 .relocate_node(
1811 Uuid::parse_str(ENTRY2_ID).unwrap(),
1812 &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1813 &[Uuid::parse_str(GROUP2_ID).unwrap()],
1814 new_location_changed_timestamp,
1815 )
1816 .unwrap();
1817
1818 let entry2 = Group::find_entry(
1819 &destination_db.root,
1820 &[
1821 Uuid::parse_str(GROUP1_ID).unwrap(),
1822 Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1823 Uuid::parse_str(ENTRY2_ID).unwrap(),
1824 ],
1825 )
1826 .unwrap();
1827 with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1828 entry2.set_field_and_commit("Title", "entry2_modified_in_destination");
1829 });
1830 let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1831
1832 let merge_result = destination_db.merge(&source_db).unwrap();
1833 assert_eq!(merge_result.warnings.len(), 0);
1834 assert_eq!(merge_result.events.len(), 2);
1835
1836 let group_count_after = get_all_groups(&destination_db.root).len();
1837 let entry_count_after = get_all_entries(&destination_db.root).len();
1838 assert_eq!(group_count_after, group_count_before);
1839 assert_eq!(entry_count_after, entry_count_before);
1840
1841 let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1842 assert_eq!(moved_entry_location.len(), 2);
1843 assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1844 assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1845
1846 let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_destination"]);
1847 let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1848 assert_eq!(ts1, entry_modified_timestamp,);
1849 let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1850 assert_eq!(ts2, new_location_changed_timestamp);
1851 }
1852
1853 #[test]
1854 fn test_entry_relocation_in_destination_and_update() {
1855 let mut destination_db = create_test_database();
1856 let source_db = destination_db.clone();
1857
1858 let group_count_before = get_all_groups(&destination_db.root).len();
1859 let entry_count_before = get_all_entries(&destination_db.root).len();
1860
1861 let entry2 = Group::find_entry(
1862 &source_db.root,
1863 &[
1864 Uuid::parse_str(GROUP1_ID).unwrap(),
1865 Uuid::parse_str(SUBGROUP1_ID).unwrap(),
1866 Uuid::parse_str(ENTRY2_ID).unwrap(),
1867 ],
1868 )
1869 .unwrap();
1870 with_node_mut::<Entry, _, _>(&entry2, |entry2| {
1871 entry2.set_field_and_commit("Title", "entry2_modified_in_source");
1872 });
1873 let entry_modified_timestamp = entry2.borrow().get_times().get_last_modification().unwrap();
1874
1875 thread::sleep(time::Duration::from_secs(1));
1876 let new_location_changed_timestamp = Times::now();
1877
1878 destination_db
1879 .relocate_node(
1880 Uuid::parse_str(ENTRY2_ID).unwrap(),
1881 &[Uuid::parse_str(GROUP1_ID).unwrap(), Uuid::parse_str(SUBGROUP1_ID).unwrap()],
1882 &[Uuid::parse_str(GROUP2_ID).unwrap()],
1883 new_location_changed_timestamp,
1884 )
1885 .unwrap();
1886
1887 let merge_result = destination_db.merge(&source_db).unwrap();
1888 assert_eq!(merge_result.warnings.len(), 0);
1889 assert_eq!(merge_result.events.len(), 1);
1890
1891 let group_count_after = get_all_groups(&destination_db.root).len();
1892 let entry_count_after = get_all_entries(&destination_db.root).len();
1893 assert_eq!(group_count_after, group_count_before);
1894 assert_eq!(entry_count_after, entry_count_before);
1895
1896 let moved_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1897 assert_eq!(moved_entry_location.len(), 2);
1898 assert_eq!(&moved_entry_location[0].to_string(), ROOT_GROUP_ID);
1899 assert_eq!(&moved_entry_location[1].to_string(), GROUP2_ID);
1900
1901 let moved_entry = get_entry(&destination_db, &["group2", "entry2_modified_in_source"]);
1902 let ts1 = moved_entry.borrow().get_times().get_last_modification().unwrap();
1903 assert_eq!(ts1, entry_modified_timestamp,);
1904 let ts2 = moved_entry.borrow().get_times().get_location_changed().unwrap();
1905 assert_eq!(ts2, new_location_changed_timestamp);
1906 }
1907
1908 #[test]
1909 fn test_entry_relocation_new_group() {
1910 let mut destination_db = create_test_database();
1911
1912 let entry_count_before = get_all_entries(&destination_db.root).len();
1913 let group_count_before = get_all_groups(&destination_db.root).len();
1914
1915 let source_db = destination_db.clone();
1916 let mut new_group = Group::new("new_group");
1917 let new_group_uuid = new_group.uuid;
1918
1919 let mut new_entry = Entry::default();
1920 let entry_uuid = new_entry.uuid;
1921 new_entry.set_field_and_commit("Title", "entry1");
1922
1923 thread::sleep(time::Duration::from_secs(1));
1924 new_entry.times.set_location_changed(Some(Times::now()));
1925 new_entry.update_history();
1928 new_group.add_child(rc_refcell_node(new_entry), 0);
1929 group_add_child(&source_db.root, rc_refcell_node(new_group), 0).unwrap();
1930
1931 let merge_result = destination_db.merge(&source_db).unwrap();
1932 assert_eq!(merge_result.warnings.len(), 0);
1933 assert_eq!(merge_result.events.len(), 2);
1934
1935 let entry_count_after = get_all_entries(&destination_db.root).len();
1936 let group_count_after = get_all_groups(&destination_db.root).len();
1937 assert_eq!(entry_count_after, entry_count_before + 1);
1938 assert_eq!(group_count_after, group_count_before + 1);
1939
1940 let created_entry_location = Group::find_node_location(&destination_db.root, entry_uuid).unwrap();
1941 assert_eq!(created_entry_location.len(), 2);
1942 assert_eq!(&created_entry_location[0].to_string(), ROOT_GROUP_ID);
1943 assert_eq!(created_entry_location[1], new_group_uuid);
1944 }
1945
1946 #[test]
1947 fn test_group_relocation() {
1948 let mut destination_db = create_test_database();
1949 let source_db = destination_db.clone();
1950
1951 let entry_count_before = get_all_entries(&destination_db.root).len();
1952 let group_count_before = get_all_groups(&destination_db.root).len();
1953
1954 let source_group_1 = get_group(&source_db, &["group1"]);
1955 let source_sub_group_1 = with_node_mut::<Group, _, _>(&source_group_1, |g| g.remove_node(Uuid::parse_str(SUBGROUP1_ID).unwrap()))
1956 .unwrap()
1957 .unwrap();
1958 assert!(node_is_group(&source_sub_group_1));
1959 thread::sleep(time::Duration::from_secs(1));
1960 let new_location_changed_timestamp = Times::now();
1961 source_sub_group_1
1962 .borrow_mut()
1963 .get_times_mut()
1964 .set_location_changed(Some(new_location_changed_timestamp));
1965
1966 let source_group_2 = get_group(&source_db, &["group2"]);
1967 group_add_child(&source_group_2, source_sub_group_1, 0).unwrap();
1968
1969 let merge_result = destination_db.merge(&source_db).unwrap();
1970 assert_eq!(merge_result.warnings.len(), 0);
1971 assert_eq!(merge_result.events.len(), 1);
1972
1973 let entry_count_after = get_all_entries(&destination_db.root).len();
1974 let group_count_after = get_all_groups(&destination_db.root).len();
1975 assert_eq!(entry_count_after, entry_count_before);
1976 assert_eq!(group_count_after, group_count_before);
1977
1978 let created_entry_location = Group::find_node_location(&destination_db.root, Uuid::parse_str(ENTRY2_ID).unwrap()).unwrap();
1979 assert_eq!(created_entry_location.len(), 3);
1980 assert_eq!(created_entry_location[0], destination_db.root.borrow().get_uuid());
1981 assert_eq!(&created_entry_location[1].to_string(), GROUP2_ID);
1982 assert_eq!(&created_entry_location[2].to_string(), SUBGROUP1_ID);
1983
1984 let relocated_group = get_group(&destination_db, &["group2", "subgroup1"]);
1985 let ts = relocated_group.borrow().get_times().get_location_changed().unwrap();
1986 assert_eq!(ts, new_location_changed_timestamp);
1987 }
1988
1989 #[test]
1990 fn test_update_in_destination_no_conflict() {
1991 let mut destination_db = create_test_database();
1992 let source_db = destination_db.clone();
1993
1994 let entry_count_before = get_all_entries(&destination_db.root).len();
1995 let group_count_before = get_all_groups(&destination_db.root).len();
1996
1997 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
1998 with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
1999
2000 let merge_result = destination_db.merge(&source_db).unwrap();
2001 assert_eq!(merge_result.warnings.len(), 0);
2002 assert_eq!(merge_result.events.len(), 0);
2003
2004 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2005 let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2006 assert!(merged_history.is_ordered());
2007 assert_eq!(merged_history.entries.len(), 2);
2008 let merged_entry = &merged_history.entries[1];
2009 assert_eq!(merged_entry.get_title(), Some("entry1"));
2010
2011 let entry_count_after = get_all_entries(&destination_db.root).len();
2012 let group_count_after = get_all_groups(&destination_db.root).len();
2013 assert_eq!(entry_count_after, entry_count_before);
2014 assert_eq!(group_count_after, group_count_before);
2015
2016 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2017 assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2018 }
2019
2020 #[test]
2021 fn test_update_in_source_no_conflict() {
2022 let mut destination_db = create_test_database();
2023 let source_db = destination_db.clone();
2024
2025 let entry_count_before = get_all_entries(&destination_db.root).len();
2026 let group_count_before = get_all_groups(&destination_db.root).len();
2027
2028 let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2029 with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated")).unwrap();
2030
2031 let merge_result = destination_db.merge(&source_db).unwrap();
2032 assert_eq!(merge_result.warnings.len(), 0);
2033 assert_eq!(merge_result.events.len(), 1);
2034
2035 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2036 let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2037 assert!(merged_history.is_ordered());
2038 assert_eq!(merged_history.entries.len(), 2);
2039 let merged_entry = &merged_history.entries[1];
2040 assert_eq!(merged_entry.get_title(), Some("entry1"));
2041
2042 let entry_count_after = get_all_entries(&destination_db.root).len();
2043 let group_count_after = get_all_groups(&destination_db.root).len();
2044 assert_eq!(entry_count_after, entry_count_before);
2045 assert_eq!(group_count_after, group_count_before);
2046
2047 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2048 assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
2049 }
2050
2051 #[test]
2052 fn test_update_with_conflicts() {
2053 let mut destination_db = create_test_database();
2054 let source_db = destination_db.clone();
2055
2056 let entry_count_before = get_all_entries(&destination_db.root).len();
2057 let group_count_before = get_all_groups(&destination_db.root).len();
2058
2059 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2060 with_node_mut::<Entry, _, _>(&entry, |e| e.set_field_and_commit("Title", "entry1_updated_from_destination")).unwrap();
2061
2062 let entry = with_node::<Group, _, _>(&source_db.root, |group| group.entries()).unwrap()[0].clone();
2063 with_node_mut::<Entry, _, _>(&entry, |entry| entry.set_field_and_commit("Title", "entry1_updated_from_source")).unwrap();
2064
2065 let merge_result = destination_db.merge(&source_db).unwrap();
2066 assert_eq!(merge_result.warnings.len(), 0);
2067 assert_eq!(merge_result.events.len(), 1);
2068
2069 let entry_count_after = get_all_entries(&destination_db.root).len();
2070 let group_count_after = get_all_groups(&destination_db.root).len();
2071 assert_eq!(entry_count_after, entry_count_before);
2072 assert_eq!(group_count_after, group_count_before);
2073
2074 let entry = with_node::<Group, _, _>(&destination_db.root, |group| group.entries()).unwrap()[0].clone();
2075 assert_eq!(entry.borrow().get_title(), Some("entry1_updated_from_source"));
2076
2077 let merged_history = with_node::<Entry, _, _>(&entry, |entry| entry.history.clone()).unwrap().unwrap();
2078 assert!(merged_history.is_ordered());
2079 assert_eq!(merged_history.entries.len(), 3);
2080 let merged_entry = &merged_history.entries[1];
2081 assert_eq!(merged_entry.get_title(), Some("entry1_updated_from_destination"));
2082
2083 let merge_result = destination_db.merge(&destination_db.clone()).unwrap();
2085 assert_eq!(merge_result.warnings.len(), 0);
2086 assert_eq!(merge_result.events.len(), 0);
2087 }
2088
2089 #[test]
2090 fn test_group_update_in_source() {
2091 let mut destination_db = create_test_database();
2092 let source_db = destination_db.clone();
2093
2094 let entry_count_before = get_all_entries(&destination_db.root).len();
2095 let group_count_before = get_all_groups(&destination_db.root).len();
2096
2097 let group = get_group(&source_db, &["group1", "subgroup1"]);
2098 group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2099 thread::sleep(time::Duration::from_secs(1));
2102 let new_modification_timestamp = Times::now();
2103 group
2104 .borrow_mut()
2105 .get_times_mut()
2106 .set_last_modification(Some(new_modification_timestamp));
2107
2108 let merge_result = destination_db.merge(&source_db).unwrap();
2109 assert_eq!(merge_result.warnings.len(), 0);
2110 assert_eq!(merge_result.events.len(), 1);
2111
2112 let entry_count_after = get_all_entries(&destination_db.root).len();
2113 let group_count_after = get_all_groups(&destination_db.root).len();
2114 assert_eq!(entry_count_after, entry_count_before);
2115 assert_eq!(group_count_after, group_count_before);
2116
2117 let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2118 assert_eq!(modified_group.borrow().get_title().unwrap(), "subgroup1_updated_name");
2119 let ts = modified_group.borrow().get_times().get_last_modification();
2120 assert_eq!(ts, Some(new_modification_timestamp));
2121 }
2122
2123 #[test]
2124 fn test_group_update_in_destination() {
2125 let mut destination_db = create_test_database();
2126 let source_db = destination_db.clone();
2127
2128 let entry_count_before = get_all_entries(&destination_db.root).len();
2129 let group_count_before = get_all_groups(&destination_db.root).len();
2130
2131 let group = get_group(&destination_db, &["group1", "subgroup1"]);
2132 group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2133 thread::sleep(time::Duration::from_secs(1));
2136 let new_modification_timestamp = Times::now();
2137 group
2138 .borrow_mut()
2139 .get_times_mut()
2140 .set_last_modification(Some(new_modification_timestamp));
2141
2142 let merge_result = destination_db.merge(&source_db).unwrap();
2143 assert_eq!(merge_result.warnings.len(), 0);
2144 assert_eq!(merge_result.events.len(), 0);
2145
2146 let entry_count_after = get_all_entries(&destination_db.root).len();
2147 let group_count_after = get_all_groups(&destination_db.root).len();
2148 assert_eq!(entry_count_after, entry_count_before);
2149 assert_eq!(group_count_after, group_count_before);
2150
2151 let modified_group = get_group(&destination_db, &["group1", "subgroup1_updated_name"]);
2152 assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2153 assert_eq!(
2154 modified_group.borrow().get_times().get_last_modification(),
2155 Some(new_modification_timestamp),
2156 );
2157 }
2158
2159 #[test]
2160 fn test_group_update_and_relocation() {
2161 let mut destination_db = create_test_database();
2162 let source_db = destination_db.clone();
2163
2164 let entry_count_before = get_all_entries(&destination_db.root).len();
2165 let group_count_before = get_all_groups(&destination_db.root).len();
2166
2167 let group = get_group(&source_db, &["group1", "subgroup1"]);
2168 group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2169 thread::sleep(time::Duration::from_secs(1));
2172 let new_modification_timestamp = Times::now();
2173 group
2174 .borrow_mut()
2175 .get_times_mut()
2176 .set_last_modification(Some(new_modification_timestamp));
2177
2178 source_db
2179 .relocate_node(
2180 Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2181 &[Uuid::parse_str(GROUP1_ID).unwrap()],
2182 &[Uuid::parse_str(GROUP2_ID).unwrap()],
2183 new_modification_timestamp,
2184 )
2185 .unwrap();
2186
2187 let merge_result = destination_db.merge(&source_db).unwrap();
2188 assert_eq!(merge_result.warnings.len(), 0);
2189 assert_eq!(merge_result.events.len(), 2);
2190
2191 let entry_count_after = get_all_entries(&destination_db.root).len();
2192 let group_count_after = get_all_groups(&destination_db.root).len();
2193 assert_eq!(entry_count_after, entry_count_before);
2194 assert_eq!(group_count_after, group_count_before);
2195
2196 let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2197 assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2198 assert_eq!(
2199 modified_group.borrow().get_times().get_last_modification(),
2200 Some(new_modification_timestamp),
2201 );
2202 }
2203
2204 #[test]
2205 fn test_group_update_in_destination_and_relocation_in_source() {
2206 let mut destination_db = create_test_database();
2207 let source_db = destination_db.clone();
2208
2209 let entry_count_before = get_all_entries(&destination_db.root).len();
2210 let group_count_before = get_all_groups(&destination_db.root).len();
2211
2212 let group = get_group(&source_db, &["group1", "subgroup1"]);
2213 group.borrow_mut().set_title(Some("subgroup1_updated_name"));
2214 thread::sleep(time::Duration::from_secs(1));
2217 let new_modification_timestamp = Times::now();
2218 group
2219 .borrow_mut()
2220 .get_times_mut()
2221 .set_last_modification(Some(new_modification_timestamp));
2222
2223 thread::sleep(time::Duration::from_secs(1));
2224 let new_location_changed_timestamp = Times::now();
2225 destination_db
2226 .relocate_node(
2227 Uuid::parse_str(SUBGROUP1_ID).unwrap(),
2228 &[Uuid::parse_str(GROUP1_ID).unwrap()],
2229 &[Uuid::parse_str(GROUP2_ID).unwrap()],
2230 new_location_changed_timestamp,
2231 )
2232 .unwrap();
2233
2234 let merge_result = destination_db.merge(&source_db).unwrap();
2235 assert_eq!(merge_result.warnings.len(), 0);
2236 assert_eq!(merge_result.events.len(), 1);
2237
2238 let entry_count_after = get_all_entries(&destination_db.root).len();
2239 let group_count_after = get_all_groups(&destination_db.root).len();
2240 assert_eq!(entry_count_after, entry_count_before);
2241 assert_eq!(group_count_after, group_count_before);
2242
2243 let modified_group = get_group(&destination_db, &["group2", "subgroup1_updated_name"]);
2244 assert_eq!(modified_group.borrow().get_title(), Some("subgroup1_updated_name"));
2245 assert_eq!(
2246 modified_group.borrow().get_times().get_last_modification(),
2247 Some(new_modification_timestamp)
2248 );
2249 assert_eq!(
2250 modified_group.borrow().get_times().get_location_changed(),
2251 Some(new_location_changed_timestamp)
2252 );
2253 }
2254
2255 #[test]
2256 fn test_icon_added_in_source() {
2257 let mut destination_db = create_test_database();
2258 let mut source_db = destination_db.clone();
2259
2260 let new_icon_id = Uuid::new_v4();
2261 source_db.meta.custom_icons.insert(
2262 new_icon_id,
2263 CustomIcon::new(new_icon_id, None, Some(Times::now()), vec![1, 2, 3, 4]),
2264 );
2265 let entry = search_node_by_uuid_with_specific_type::<Entry>(&source_db.root, Uuid::parse_str(ENTRY1_ID).unwrap()).unwrap();
2266 with_node_mut::<Entry, _, _>(&entry, |entry| {
2267 entry.custom_icon = Some(new_icon_id);
2268 entry.get_times_mut().set_last_modification(Some(Times::now()));
2269 });
2270
2271 let merge_result = destination_db.merge(&source_db).unwrap();
2272 assert_eq!(merge_result.warnings.len(), 0);
2273 assert_eq!(merge_result.events.len(), 2);
2274
2275 assert!(destination_db.meta.custom_icon(new_icon_id).is_some());
2276 }
2277
2278 #[test]
2279 fn test_icon_updated_in_source() {
2280 let mut destination_db = create_test_database();
2281
2282 let icon_id = Uuid::new_v4();
2283 destination_db
2284 .meta
2285 .custom_icons
2286 .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2287
2288 let mut source_db = destination_db.clone();
2289
2290 let source_icon = source_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2291 source_icon.data = vec![5, 6, 7, 8];
2292 source_icon.last_modification_time = Some(Times::now());
2293
2294 let merge_result = destination_db.merge(&source_db).unwrap();
2295 assert_eq!(merge_result.warnings.len(), 0);
2296 assert_eq!(merge_result.events.len(), 1);
2297
2298 let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2299 assert_eq!(icon.data, vec![5, 6, 7, 8]);
2300 }
2301
2302 #[test]
2303 fn test_icon_updated_in_destination() {
2304 let mut destination_db = create_test_database();
2305
2306 let icon_id = Uuid::new_v4();
2307 destination_db
2308 .meta
2309 .custom_icons
2310 .insert(icon_id, CustomIcon::new(icon_id, None, Some(Times::epoch()), vec![1, 2, 3, 4]));
2311
2312 let source_db = destination_db.clone();
2313
2314 let destination_icon = destination_db.meta.custom_icons.get_mut(&icon_id).unwrap();
2315 destination_icon.data = vec![5, 6, 7, 8];
2316 destination_icon.last_modification_time = Some(Times::now());
2317
2318 let merge_result = destination_db.merge(&source_db).unwrap();
2319 assert_eq!(merge_result.warnings.len(), 0);
2320 assert_eq!(merge_result.events.len(), 0);
2321
2322 let icon = destination_db.meta.custom_icon(icon_id).unwrap();
2323 assert_eq!(icon.data, vec![5, 6, 7, 8]);
2324 }
2325}