1use crate::db::{CustomData, IconId, Times, entry::Entry, node::*, rc_refcell_node};
2use uuid::Uuid;
3
4#[cfg(feature = "merge")]
5use crate::db::merge::{MergeError, MergeEvent, MergeEventType, MergeLog};
6
7pub(crate) enum SearchField {
8 #[cfg(any(test, feature = "merge"))]
9 Uuid,
10 Title,
11}
12
13impl SearchField {
14 pub(crate) fn matches(&self, node: &NodePtr, field_value: &str) -> bool {
15 match self {
16 #[cfg(any(test, feature = "merge"))]
17 SearchField::Uuid => node.borrow().get_uuid().to_string() == field_value,
18 SearchField::Title => match node.borrow().get_title() {
19 Some(title) => title == field_value,
20 None => false,
21 },
22 }
23 }
24}
25
26#[derive(Debug, Clone)]
28#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
29pub struct Group {
30 pub(crate) uuid: Uuid,
32
33 pub(crate) name: Option<String>,
35
36 pub(crate) notes: Option<String>,
38
39 pub(crate) icon_id: Option<IconId>,
41
42 pub(crate) custom_icon_uuid: Option<Uuid>,
44
45 pub(crate) children: Vec<SerializableNodePtr>,
47
48 pub(crate) times: Times,
50
51 pub(crate) custom_data: CustomData,
53
54 pub(crate) is_expanded: bool,
56
57 pub(crate) default_autotype_sequence: Option<String>,
59
60 pub(crate) enable_autotype: Option<String>,
63
64 pub(crate) enable_searching: Option<String>,
67
68 pub(crate) last_top_visible_entry: Option<Uuid>,
72
73 pub(crate) parent: Option<Uuid>,
74}
75
76impl Default for Group {
77 fn default() -> Self {
78 Self {
79 uuid: Uuid::new_v4(),
80 name: Some("Default Group".to_string()),
81 notes: None,
82 icon_id: Some(IconId::FOLDER),
83 custom_icon_uuid: None,
84 children: Vec::new(),
85 times: Times::new(),
86 custom_data: CustomData::default(),
87 is_expanded: false,
88 default_autotype_sequence: None,
89 enable_autotype: None,
90 enable_searching: None,
91 last_top_visible_entry: None,
92 parent: None,
93 }
94 }
95}
96
97impl PartialEq for Group {
98 fn eq(&self, other: &Self) -> bool {
99 self.uuid == other.uuid
100 && self.compare_children(other)
101 && self.times == other.times
102 && self.name == other.name
103 && self.notes == other.notes
104 && self.icon_id == other.icon_id
105 && self.custom_icon_uuid == other.custom_icon_uuid
106 && self.is_expanded == other.is_expanded
107 && self.default_autotype_sequence == other.default_autotype_sequence
108 && self.enable_autotype == other.enable_autotype
109 && self.enable_searching == other.enable_searching
110 && self.last_top_visible_entry == other.last_top_visible_entry
111 && self.custom_data == other.custom_data
112 }
114}
115
116impl Eq for Group {}
117
118impl Node for Group {
119 fn duplicate(&self) -> NodePtr {
120 let mut new_group = self.clone();
121 new_group.parent = None;
122 new_group.children = self
123 .children
124 .iter()
125 .map(|child| {
126 let child = child.borrow().duplicate();
127 child.borrow_mut().set_parent(Some(new_group.uuid));
128 child.into()
129 })
130 .collect();
131 rc_refcell_node(new_group)
132 }
133
134 fn get_uuid(&self) -> Uuid {
135 self.uuid
136 }
137
138 fn set_uuid(&mut self, uuid: Uuid) {
139 self.uuid = uuid;
140 }
141
142 fn get_title(&self) -> Option<&str> {
143 self.name.as_deref()
144 }
145
146 fn set_title(&mut self, title: Option<&str>) {
147 self.name = title.map(std::string::ToString::to_string);
148 }
149
150 fn get_notes(&self) -> Option<&str> {
151 self.notes.as_deref()
152 }
153
154 fn set_notes(&mut self, notes: Option<&str>) {
155 self.notes = notes.map(std::string::ToString::to_string);
156 }
157
158 fn get_icon_id(&self) -> Option<IconId> {
159 self.icon_id
160 }
161
162 fn set_icon_id(&mut self, icon_id: Option<IconId>) {
163 self.icon_id = icon_id;
164 }
165
166 fn get_custom_icon_uuid(&self) -> Option<Uuid> {
167 self.custom_icon_uuid
168 }
169
170 fn get_times(&self) -> &Times {
171 &self.times
172 }
173
174 fn get_times_mut(&mut self) -> &mut Times {
175 &mut self.times
176 }
177
178 fn get_parent(&self) -> Option<Uuid> {
179 self.parent
180 }
181
182 fn set_parent(&mut self, parent: Option<Uuid>) {
183 self.parent = parent;
184 }
185}
186
187impl Group {
188 pub fn new(name: &str) -> Group {
189 Group {
190 name: Some(name.to_string()),
191 ..Group::default()
192 }
193 }
194
195 pub fn get_children(&self) -> Vec<NodePtr> {
196 self.children.iter().map(|c| c.into()).collect()
197 }
198
199 fn compare_children(&self, other: &Self) -> bool {
200 if self.children.len() != other.children.len() {
201 return false;
202 }
203 self.children.iter().zip(other.children.iter()).all(|(a, b)| {
204 if let (Some(a), Some(b)) = (a.borrow().downcast_ref::<Group>(), b.borrow().downcast_ref::<Group>()) {
205 a == b
206 } else if let (Some(a), Some(b)) = (a.borrow().downcast_ref::<Entry>(), b.borrow().downcast_ref::<Entry>()) {
207 a == b
208 } else {
209 false
210 }
211 })
212 }
213
214 pub fn set_name(&mut self, name: &str) {
215 self.name = Some(name.to_string());
216 }
217
218 pub fn add_child(&mut self, child: NodePtr, index: usize) {
219 child.borrow_mut().set_parent(Some(self.get_uuid()));
220 if index < self.children.len() {
221 self.children.insert(index, child.into());
222 } else {
223 self.children.push(child.into());
224 }
225 }
226
227 pub fn get(group: &NodePtr, path: &[&str]) -> Option<NodePtr> {
244 Self::get_internal(group, path, SearchField::Title)
245 }
246
247 #[cfg(any(test, feature = "merge"))]
248 pub(crate) fn get_by_uuid<T: AsRef<str>>(group: &NodePtr, path: &[T]) -> Option<NodePtr> {
249 Self::get_internal(group, path, SearchField::Uuid)
250 }
251
252 fn get_internal<T: AsRef<str>>(group: &NodePtr, path: &[T], search_field: SearchField) -> Option<NodePtr> {
253 if path.is_empty() {
254 Some(group.clone())
255 } else if path.len() == 1 {
256 group_get_children(group)
257 .unwrap_or_default()
258 .iter()
259 .find_map(|node| match search_field.matches(node, path[0].as_ref()) {
260 true => Some(node.clone()),
261 false => None,
262 })
263 } else {
264 let head = path[0].as_ref();
265 let tail = &path[1..path.len()];
266 let head_group = group_get_children(group).unwrap_or_default().iter().find_map(|node| {
267 if node_is_group(node) && search_field.matches(node, head) {
268 Some(node.clone())
269 } else {
270 None
271 }
272 })?;
273
274 Self::get_internal(&head_group, tail, search_field)
275 }
276 }
277
278 #[cfg(feature = "merge")]
279 pub(crate) fn find_group(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
280 let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
281 let node_ref = Self::get_by_uuid(group, &path)?;
282 if node_is_group(&node_ref) { Some(node_ref) } else { None }
283 }
284
285 #[cfg(feature = "merge")]
286 pub(crate) fn find_entry(group: &NodePtr, path: &[Uuid]) -> Option<NodePtr> {
287 let path: Vec<String> = path.iter().map(|p| p.to_string()).collect();
288 let node_ref = Self::get_by_uuid(group, &path)?;
289 if node_is_entry(&node_ref) { Some(node_ref) } else { None }
290 }
291
292 pub fn entries(&self) -> Vec<NodePtr> {
293 let mut response: Vec<NodePtr> = vec![];
294 for node in &self.children {
295 if node_is_entry(node) {
296 response.push(node.into());
297 }
298 }
299 response
300 }
301
302 pub fn groups(&self) -> Vec<NodePtr> {
303 let mut response: Vec<NodePtr> = vec![];
304 for node in &self.children {
305 if node_is_group(node) {
306 response.push(node.into());
307 }
308 }
309 response
310 }
311
312 #[cfg(feature = "merge")]
313 pub(crate) fn remove_node(&mut self, uuid: Uuid) -> Result<NodePtr, MergeError> {
314 let mut removed_node = None;
315 self.children.retain(|c| {
316 if c.borrow().get_uuid() == uuid {
317 removed_node = Some(NodePtr::from(c));
318 return false;
319 }
320 true
321 });
322
323 let title = self.get_title().unwrap_or("No title").to_string();
324 let node = removed_node.ok_or(MergeError::GenericError(format!("Could not find node {uuid} in group \"{title}\"")))?;
325 Ok(node)
326 }
327
328 #[cfg(feature = "merge")]
329 pub(crate) fn find_node_location(parent: &NodePtr, id: Uuid) -> Option<Vec<Uuid>> {
330 let parent_uuid = parent.borrow().get_uuid();
331 let mut current_location = vec![parent_uuid];
332 for node in &group_get_children(parent).unwrap_or_default() {
333 let node_uuid = node.borrow().get_uuid();
334 if node_is_entry(node) {
335 if node_uuid == id {
336 return Some(current_location);
338 }
339 } else if node_is_group(node) {
340 if node_uuid == id {
341 return Some(current_location);
343 }
344 if let Some(mut location) = Self::find_node_location(node, id) {
345 current_location.append(&mut location);
346 return Some(current_location);
347 }
348 }
349 }
350 None
351 }
352
353 #[cfg(feature = "merge")]
354 pub(crate) fn merge_with(group: &NodePtr, other: &NodePtr) -> Result<MergeLog, MergeError> {
355 let mut log = MergeLog::default();
356
357 let group_uuid = group.borrow().get_uuid();
358
359 let other = other.borrow();
360 let other = other
361 .downcast_ref::<Group>()
362 .ok_or(MergeError::GenericError("Could not downcast node to group".to_string()))?;
363 let source_last_modification = match other.times.get_last_modification() {
364 Some(t) => t,
365 None => {
366 log.warnings
367 .push(format!("Group {group_uuid} did not have a last modification timestamp"));
368 Times::epoch()
369 }
370 };
371 let destination_last_modification = match group.borrow().get_times().get_last_modification() {
372 Some(t) => t,
373 None => {
374 log.warnings
375 .push(format!("Group {group_uuid} did not have a last modification timestamp"));
376 Times::now()
377 }
378 };
379 if destination_last_modification == source_last_modification {
380 if group.borrow().downcast_ref::<Group>().unwrap()._has_diverged_from(other) {
381 return Err(MergeError::GroupModificationTimeNotUpdated(other.uuid.to_string()));
385 }
386 return Ok(log);
387 }
388 if destination_last_modification > source_last_modification {
389 return Ok(log);
390 }
391 with_node_mut::<Group, _, _>(group, |group| {
392 group.name = other.name.clone();
393 group.notes = other.notes.clone();
394 group.icon_id = other.icon_id;
395 group.custom_icon_uuid = other.custom_icon_uuid;
396 group.custom_data = other.custom_data.clone();
397 let current_times = group.times.clone();
399 group.times = other.times.clone();
400 if let Some(t) = current_times.get_location_changed() {
401 group.times.set_location_changed(Some(t));
402 }
403 group.is_expanded = other.is_expanded;
404 group.default_autotype_sequence = other.default_autotype_sequence.clone();
405 group.enable_autotype = other.enable_autotype.clone();
406 group.enable_searching = other.enable_searching.clone();
407 group.last_top_visible_entry = other.last_top_visible_entry;
408 })
409 .unwrap();
410 log.events.push(MergeEvent {
411 event_type: MergeEventType::GroupUpdated,
412 node_uuid: group_uuid,
413 });
414 Ok(log)
415 }
416
417 #[cfg(feature = "merge")]
418 pub(crate) fn _has_diverged_from(&self, other: &Group) -> bool {
419 let new_times = Times::new();
420 let mut self_purged = self.clone();
421 self_purged.times = new_times.clone();
422 self_purged.children = vec![];
423 let mut other_purged = other.clone();
424 other_purged.times = new_times.clone();
425 other_purged.children = vec![];
426 !self_purged.eq(&other_purged)
427 }
428
429 pub fn reset_children(&mut self, children: Vec<NodePtr>) {
430 let uuid = self.get_uuid();
431 children.iter().for_each(|c| c.borrow_mut().set_parent(Some(uuid)));
432 self.children = children.into_iter().map(|c| c.into()).collect();
433 }
434
435 #[cfg(feature = "merge")]
436 fn replace_entry(root: &NodePtr, entry: &NodePtr) -> Option<()> {
437 let uuid = entry.borrow().get_uuid();
438 let target_entry = search_node_by_uuid_with_specific_type::<Entry>(root, uuid);
439 Entry::entry_replaced_with(target_entry.as_ref()?, entry)
440 }
441
442 #[cfg(feature = "merge")]
443 pub(crate) fn has_group(&self, uuid: Uuid) -> bool {
444 self.children.iter().any(|n| n.borrow().get_uuid() == uuid && node_is_group(n))
445 }
446
447 #[cfg(feature = "merge")]
448 fn get_or_create_group(group: &NodePtr, location: &[Uuid], create_groups: bool) -> crate::Result<NodePtr> {
449 if location.is_empty() {
450 return Err("Empty location.".into());
451 }
452
453 let mut remaining_location = location.to_owned();
454 remaining_location.remove(0);
455
456 if remaining_location.is_empty() {
457 return Ok(group.clone());
458 }
459
460 let next_location = &remaining_location[0];
461 let mut next_location_uuid = *next_location;
462
463 if !with_node::<Group, _, _>(group, |g| g.has_group(next_location_uuid)).unwrap() && create_groups {
464 let mut current_group: Option<NodePtr> = None;
465 for i in (0..(remaining_location.len())).rev() {
466 let mut new_group = Group::new(&remaining_location[i].to_string());
467 new_group.set_uuid(remaining_location[i]);
468 if let Some(current_group) = current_group {
469 let count = group_get_children(group).map(|c| c.len()).unwrap_or(0);
470 new_group.add_child(current_group, count);
471 }
472 current_group = Some(rc_refcell_node(new_group));
473 }
474
475 if let Some(current_group) = current_group {
476 next_location_uuid = current_group.borrow().get_uuid();
477 let count = group_get_children(group).map_or(0, |c| c.len());
478 group_add_child(group, current_group, count)?;
479 } else {
480 return Err("Could not create group.".into());
481 }
482 }
483
484 let mut target = None;
485 for node in group_get_children(group).unwrap_or_default().iter() {
486 if node_is_group(node) && node.borrow().get_uuid() == next_location_uuid {
487 target = Some(node.clone());
488 break;
489 }
490 }
491
492 match &target {
493 Some(target) => Self::get_or_create_group(target, &remaining_location, create_groups),
494 None => Err("The group was not found.".into()),
495 }
496 }
497
498 #[cfg(feature = "merge")]
499 pub(crate) fn insert_entry(group: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
500 let group = Self::get_or_create_group(group, location, true)?;
501 with_node_mut::<Group, _, _>(&group, |g| {
502 let count = g.children.len();
503 g.add_child(entry, count);
504 Ok::<(), crate::Error>(())
505 })
506 .ok_or("Could not add entry")??;
507 Ok(())
508 }
509
510 #[cfg(feature = "merge")]
511 pub(crate) fn remove_entry(group: &NodePtr, uuid: Uuid, location: &[Uuid]) -> crate::Result<NodePtr> {
512 let group = Self::get_or_create_group(group, location, false)?;
513
514 let mut removed_entry: Option<NodePtr> = None;
515 let mut new_nodes: Vec<NodePtr> = vec![];
516 println!(
517 "Searching for entry {} in {}",
518 uuid,
519 group.borrow().get_title().unwrap_or("No title")
520 );
521
522 with_node::<Group, _, _>(&group, |g| {
523 for node in g.children.iter() {
524 if node_is_entry(node) {
525 let node_uuid = node.borrow().get_uuid();
526 println!("Saw entry {}", node_uuid);
527 if node_uuid != uuid {
528 new_nodes.push(NodePtr::from(node));
529 continue;
530 }
531 removed_entry = Some(NodePtr::from(node));
532 } else if node_is_group(node) {
533 new_nodes.push(NodePtr::from(node));
534 }
535 }
536 });
537
538 if let Some(entry) = removed_entry {
539 with_node_mut::<Group, _, _>(&group, |g| g.reset_children(new_nodes)).ok_or("Could not reset children")?;
540 Ok(entry)
541 } else {
542 let title = group.borrow().get_title().unwrap_or("No title").to_string();
543 Err(format!("Could not find entry {uuid} in group \"{title}\".").into())
544 }
545 }
546
547 #[cfg(feature = "merge")]
548 pub(crate) fn find_entry_location(&self, uuid: Uuid) -> Option<Vec<Uuid>> {
549 let mut current_location = vec![self.uuid];
550 for node in &self.children {
551 if node_is_entry(node) {
552 if node.borrow().get_uuid() == uuid {
553 return Some(current_location);
554 }
555 } else if let Some(g) = node.borrow().downcast_ref::<Group>() {
556 if let Some(mut location) = g.find_entry_location(uuid) {
557 current_location.append(&mut location);
558 return Some(current_location);
559 }
560 }
561 }
562 None
563 }
564
565 #[cfg(feature = "merge")]
566 pub(crate) fn add_entry(parent: &NodePtr, entry: NodePtr, location: &[Uuid]) -> crate::Result<()> {
567 if location.is_empty() {
568 panic!("TODO handle this with a Response.");
569 }
570
571 let mut remaining_location = location.to_owned();
572 remaining_location.remove(0);
573
574 if remaining_location.is_empty() {
575 with_node_mut::<Group, _, _>(parent, |g| {
576 let count = g.children.len();
577 g.add_child(entry, count);
578 Ok::<(), crate::Error>(())
579 })
580 .ok_or("Could not add entry")??;
581 return Ok(());
582 }
583
584 let next_location = remaining_location[0];
585
586 println!("Searching for group {:?}", next_location);
587 for node in group_get_children(parent).unwrap_or_default() {
588 if node_is_group(&node) {
589 if node.borrow().get_uuid() != next_location {
590 continue;
591 }
592 Self::add_entry(&node, entry, &remaining_location)?;
593 return Ok(());
594 }
595 }
596
597 let new_group = rc_refcell_node(Group::new(&next_location.to_string()));
599 new_group.borrow_mut().set_uuid(next_location);
600 Self::add_entry(&new_group, entry, &remaining_location)?;
601 let count = group_get_children(parent).map_or(0, |c| c.len());
602 group_add_child(parent, new_group, count)?;
603 Ok(())
604 }
605
606 #[cfg(feature = "merge")]
608 #[allow(clippy::too_many_lines)]
609 pub fn merge(root: &NodePtr, other_group: &NodePtr) -> crate::Result<MergeLog> {
610 let mut log = MergeLog::default();
611
612 let other_entries = with_node::<Group, _, _>(other_group, |g| Ok(g.get_all_entries(&[])))
613 .unwrap_or(Err(crate::Error::from("Could not downcast other group to group")))?;
614
615 for (entry, entry_location) in &other_entries {
617 let entry_uuid = entry.borrow().get_uuid();
618 let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
619
620 let existing_entry = match the_entry {
621 Some(e) => e,
622 None => continue,
623 };
624
625 let the_entry_location = with_node::<Group, _, _>(root, |g| Ok(g.find_entry_location(entry_uuid)))
626 .unwrap_or(Err("Could not downcast root to group"))?;
627
628 let existing_entry_location = match the_entry_location {
629 Some(l) => l,
630 None => continue,
631 };
632
633 let source_location_changed_time = if let Some(t) = entry.borrow().get_times().get_location_changed() {
634 t
635 } else {
636 log.warnings
637 .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
638 Times::epoch()
639 };
640 let destination_location_changed = if let Some(t) = existing_entry.borrow().get_times().get_location_changed() {
641 t
642 } else {
643 log.warnings
644 .push(format!("Entry {entry_uuid} did not have a location updated timestamp"));
645 Times::now()
646 };
647 if source_location_changed_time > destination_location_changed {
648 log.events.push(MergeEvent {
649 event_type: MergeEventType::EntryLocationUpdated,
650 node_uuid: entry_uuid,
651 });
652 Self::remove_entry(root, entry_uuid, &existing_entry_location)?;
653 Self::insert_entry(root, entry.borrow().duplicate(), entry_location)?;
654 }
655 }
656
657 for (entry, entry_location) in &other_entries {
659 let entry_uuid = entry.borrow().get_uuid();
660 let the_entry = search_node_by_uuid_with_specific_type::<Entry>(root, entry_uuid);
661 if let Some(existing_entry) = the_entry {
662 if node_is_equals_to(&existing_entry, entry) {
663 continue;
664 }
665
666 let source_last_modification = if let Some(t) = entry.borrow().get_times().get_last_modification() {
667 t
668 } else {
669 log.warnings
670 .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
671 Times::epoch()
672 };
673 let destination_last_modification = if let Some(t) = existing_entry.borrow().get_times().get_last_modification() {
674 t
675 } else {
676 log.warnings
677 .push(format!("Entry {entry_uuid} did not have a last modification timestamp"));
678 Times::now()
679 };
680
681 if destination_last_modification == source_last_modification {
682 if !node_is_equals_to(&existing_entry, entry) {
683 return Err("Entries have the same modification time but are not the same!".into());
687 }
688 continue;
689 }
690
691 let (merged_entry, entry_merge_log) = if destination_last_modification > source_last_modification {
692 Entry::merge(&existing_entry, entry)?
693 } else {
694 Entry::merge(entry, &existing_entry)?
695 };
696 let Some(merged_entry) = merged_entry else {
697 continue;
698 };
699 if node_is_equals_to(&existing_entry, &merged_entry) {
701 continue;
702 }
703
704 Group::replace_entry(root, &merged_entry).ok_or("Could not replace entry")?;
705
706 log.events.push(MergeEvent {
707 event_type: MergeEventType::EntryUpdated,
708 node_uuid: merged_entry.borrow().get_uuid(),
709 });
710 log = log.merge_with(&entry_merge_log);
711 } else {
712 Self::add_entry(root, entry.borrow().duplicate(), entry_location)?;
713 log.events.push(MergeEvent {
715 event_type: MergeEventType::EntryCreated,
716 node_uuid: entry.borrow().get_uuid(),
717 });
718 }
719 }
720
721 Ok(log)
723 }
724
725 #[cfg(feature = "merge")]
728 pub(crate) fn get_all_entries(&self, current_location: &[Uuid]) -> Vec<(NodePtr, Vec<Uuid>)> {
729 let mut response: Vec<(NodePtr, Vec<Uuid>)> = vec![];
730 let mut new_location = current_location.to_owned();
731 new_location.push(self.uuid);
732
733 for node in &self.children {
734 if node_is_entry(node) {
735 response.push((node.into(), new_location.clone()));
736 }
737 with_node::<Group, _, _>(node, |g| {
738 let mut new_entries = g.get_all_entries(&new_location);
739 response.append(&mut new_entries);
740 });
741 }
742 response
743 }
744}
745
746#[allow(unused_imports)]
747#[cfg(test)]
748mod group_tests {
749 use super::{Entry, Group, Node, Times};
750 use crate::db::{rc_refcell_node, *};
751 use std::{thread, time};
752
753 #[cfg(feature = "merge")]
754 use crate::db::entry::entry_set_field_and_commit;
755
756 #[cfg(feature = "merge")]
757 #[test]
758 fn test_merge_idempotence() {
759 let destination_group = rc_refcell_node(Group::new("group1"));
760 let entry = rc_refcell_node(Entry::default());
761 let _entry_uuid = entry.borrow().get_uuid();
762 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
763 let count = group_get_children(&destination_group).unwrap().len();
764 group_add_child(&destination_group, entry, count).unwrap();
765
766 let source_group = destination_group.borrow().duplicate();
767
768 let sg2: NodePtr = source_group.clone();
769 let merge_result = Group::merge(&destination_group, &sg2).unwrap();
770 assert_eq!(merge_result.warnings.len(), 0);
771 assert_eq!(merge_result.events.len(), 0);
772
773 with_node::<Group, _, _>(&destination_group, |destination_group| {
774 assert_eq!(destination_group.children.len(), 1);
775 with_node::<Group, _, _>(&source_group, |source_group| {
778 assert_eq!(destination_group, source_group);
779 });
780
781 let entry = destination_group.entries()[0].clone();
782 entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
783 });
784 let merge_result = Group::merge(&destination_group, &sg2).unwrap();
785 assert_eq!(merge_result.warnings.len(), 0);
786 assert_eq!(merge_result.events.len(), 0);
787
788 let destination_group_just_after_merge = destination_group.borrow().duplicate();
789 let merge_result = Group::merge(&destination_group, &sg2).unwrap();
790 assert_eq!(merge_result.warnings.len(), 0);
791 assert_eq!(merge_result.events.len(), 0);
792
793 assert!(node_is_equals_to(&destination_group_just_after_merge, &destination_group));
796 }
797
798 #[cfg(feature = "merge")]
799 #[test]
800 fn test_merge_add_new_entry() {
801 let destination_group = rc_refcell_node(Group::new("group1"));
802 let source_group = rc_refcell_node(Group::new("group1"));
803
804 let entry = rc_refcell_node(Entry::default());
805 let entry_uuid = entry.borrow().get_uuid();
806 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
807 group_add_child(&source_group, entry, 0).unwrap();
808
809 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
810 assert_eq!(merge_result.warnings.len(), 0);
811 assert_eq!(merge_result.events.len(), 1);
812 {
813 assert_eq!(group_get_children(&destination_group).unwrap().len(), 1);
814 let new_entry = search_node_by_uuid_with_specific_type::<Entry>(&destination_group, entry_uuid);
815 assert!(new_entry.is_some());
816 assert_eq!(new_entry.unwrap().borrow().get_title().unwrap(), "entry1");
817 }
818
819 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
821 assert_eq!(merge_result.warnings.len(), 0);
822 assert_eq!(merge_result.events.len(), 0);
823 assert_eq!(group_get_children(&destination_group).unwrap().len(), 1);
824 }
825
826 #[cfg(feature = "merge")]
827 #[test]
828 fn test_merge_add_new_non_root_entry() {
829 let destination_group = rc_refcell_node(Group::new("group1"));
830 let destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
831
832 group_add_child(&destination_group, destination_sub_group, 0).unwrap();
833
834 let source_group = destination_group.borrow().duplicate();
835 let source_sub_group = with_node::<Group, _, _>(&source_group, |g| g.groups()[0].clone()).unwrap();
836
837 let entry: NodePtr = rc_refcell_node(Entry::default());
838 let _entry_uuid = entry.borrow().get_uuid();
839 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
840 let count = group_get_children(&source_sub_group).unwrap().len();
841 group_add_child(&source_sub_group, entry, count).unwrap();
842
843 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
844 assert_eq!(merge_result.warnings.len(), 0);
845 assert_eq!(merge_result.events.len(), 1);
846 let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
847 assert_eq!(destination_entries.len(), 1);
848 let (_created_entry, created_entry_location) = destination_entries.first().unwrap();
849 println!("{:?}", created_entry_location);
850 assert_eq!(created_entry_location.len(), 2);
851 }
852
853 #[cfg(feature = "merge")]
854 #[test]
855 fn test_merge_add_new_entry_new_group() {
856 let destination_group = rc_refcell_node(Group::new("group1"));
857 let _destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
858 let source_group = rc_refcell_node(Group::new("group1"));
859 let source_sub_group = rc_refcell_node(Group::new("subgroup1"));
860
861 let entry = rc_refcell_node(Entry::default());
862 let _entry_uuid = entry.borrow().get_uuid();
863 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
864 group_add_child(&source_sub_group, entry, 0).unwrap();
865 group_add_child(&source_group, source_sub_group, 0).unwrap();
866
867 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
868 assert_eq!(merge_result.warnings.len(), 0);
869 assert_eq!(merge_result.events.len(), 1);
870
871 with_node::<Group, _, _>(&destination_group, |destination_group| {
872 let destination_entries = destination_group.get_all_entries(&[]);
873 assert_eq!(destination_entries.len(), 1);
874 let (_, created_entry_location) = destination_entries.first().unwrap();
875 assert_eq!(created_entry_location.len(), 2);
876 });
877 }
878
879 #[cfg(feature = "merge")]
880 #[test]
881 fn test_merge_entry_relocation_existing_group() {
882 let entry = rc_refcell_node(Entry::default());
883 let entry_uuid = entry.borrow().get_uuid();
884 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
885
886 let destination_group = rc_refcell_node(Group::new("group1"));
887 let destination_sub_group1 = rc_refcell_node(Group::new("subgroup1"));
888 let destination_sub_group2 = rc_refcell_node(Group::new("subgroup2"));
889 let destination_sub_group2_uuid = destination_sub_group2.borrow().get_uuid();
890 group_add_child(&destination_sub_group1, entry, 0).unwrap();
891 group_add_child(&destination_group, destination_sub_group1.borrow().duplicate(), 0).unwrap();
892 group_add_child(&destination_group, destination_sub_group2.borrow().duplicate(), 1).unwrap();
893
894 let source_group = destination_group.borrow().duplicate();
895 assert_eq!(
896 with_node::<Group, _, _>(&source_group, |g| g.get_all_entries(&[])).unwrap().len(),
897 1
898 );
899
900 let destination_group_uuid = destination_group.borrow().get_uuid();
901 let destination_sub_group1_uuid = destination_sub_group1.borrow().get_uuid();
902
903 let location = vec![destination_group_uuid, destination_sub_group1_uuid];
904 let removed_entry = Group::remove_entry(&source_group, entry_uuid, &location).unwrap();
905
906 removed_entry.borrow_mut().get_times_mut().set_location_changed(Some(Times::now()));
907 assert!(
908 with_node::<Group, _, _>(&source_group, |g| g.get_all_entries(&[]))
909 .unwrap()
910 .is_empty()
911 );
912 with_node_mut::<Entry, _, _>(&removed_entry, |entry| {
915 entry.update_history();
916 });
917
918 let location = vec![destination_group_uuid, destination_sub_group2_uuid];
919
920 Group::insert_entry(&source_group, removed_entry, &location).unwrap();
921
922 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
923 assert_eq!(merge_result.warnings.len(), 0);
924 assert_eq!(merge_result.events.len(), 1);
925
926 let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
927 assert_eq!(destination_entries.len(), 1);
928 let (_moved_entry, moved_entry_location) = destination_entries.first().unwrap();
929 assert_eq!(moved_entry_location.len(), 2);
930 assert_eq!(moved_entry_location[0], destination_group_uuid);
931 assert_eq!(moved_entry_location[1], destination_sub_group2_uuid);
932 }
933
934 #[cfg(feature = "merge")]
935 #[test]
936 fn test_merge_entry_relocation_new_group() {
937 let entry = rc_refcell_node(Entry::default());
938 let _entry_uuid = entry.borrow().get_uuid();
939 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
940
941 let destination_group = rc_refcell_node(Group::new("group1"));
942 let uuid1 = destination_group.borrow().get_uuid();
943 let destination_sub_group = rc_refcell_node(Group::new("subgroup1"));
944 group_add_child(&destination_sub_group, entry.borrow().duplicate(), 0).unwrap();
945 group_add_child(&destination_group, destination_sub_group, 0).unwrap();
946
947 let source_group = destination_group.borrow().duplicate();
948 let source_sub_group = rc_refcell_node(Group::new("subgroup2"));
949 let uuid2 = source_sub_group.borrow().get_uuid();
950 thread::sleep(time::Duration::from_secs(1));
951 with_node_mut::<Entry, _, _>(&entry, |entry| {
952 entry.times.set_location_changed(Some(Times::now()));
953 entry.update_history();
956 });
957 group_add_child(&source_sub_group, entry, 0).unwrap();
958 with_node_mut::<Group, _, _>(&source_group, |g| {
959 g.reset_children(vec![]);
960 g.add_child(source_sub_group, 0);
961 })
962 .unwrap();
963
964 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
965 assert_eq!(merge_result.warnings.len(), 0);
966 assert_eq!(merge_result.events.len(), 1);
967
968 let destination_entries = with_node::<Group, _, _>(&destination_group, |g| g.get_all_entries(&[])).unwrap();
969 assert_eq!(destination_entries.len(), 1);
970 let (_, created_entry_location) = destination_entries.first().unwrap();
971 assert_eq!(created_entry_location.len(), 2);
972 assert_eq!(created_entry_location[0], uuid1);
973 assert_eq!(created_entry_location[1], uuid2);
974 }
975
976 #[cfg(feature = "merge")]
977 #[test]
978 fn test_update_in_destination_no_conflict() {
979 let destination_group = rc_refcell_node(Group::new("group1"));
980
981 let entry = rc_refcell_node(Entry::default());
982 let _entry_uuid = entry.borrow().get_uuid();
983 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
984
985 group_add_child(&destination_group, entry, 0).unwrap();
986
987 let source_group = destination_group.borrow().duplicate();
988
989 let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
990 entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
991
992 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
993 assert_eq!(merge_result.warnings.len(), 0);
994 assert_eq!(merge_result.events.len(), 0);
995
996 let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
997 assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
998 }
999
1000 #[cfg(feature = "merge")]
1001 #[test]
1002 fn test_update_in_source_no_conflict() {
1003 let destination_group = rc_refcell_node(Group::new("group1"));
1004
1005 let entry = rc_refcell_node(Entry::default());
1006 let _entry_uuid = entry.borrow().get_uuid();
1007 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
1008 group_add_child(&destination_group, entry, 0).unwrap();
1009
1010 let source_group = destination_group.borrow().duplicate();
1011
1012 let entry = with_node::<Group, _, _>(&source_group, |g| g.entries()[0].clone()).unwrap();
1013 entry_set_field_and_commit(&entry, "Title", "entry1_updated").unwrap();
1014
1015 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
1016 assert_eq!(merge_result.warnings.len(), 0);
1017 assert_eq!(merge_result.events.len(), 1);
1018
1019 let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
1020 assert_eq!(entry.borrow().get_title(), Some("entry1_updated"));
1021 }
1022
1023 #[cfg(feature = "merge")]
1024 #[test]
1025 fn test_update_with_conflicts() {
1026 let destination_group = rc_refcell_node(Group::new("group1"));
1027
1028 let entry = rc_refcell_node(Entry::default());
1029 let _entry_uuid = entry.borrow().get_uuid();
1030 entry_set_field_and_commit(&entry, "Title", "entry1").unwrap();
1031 group_add_child(&destination_group, entry, 0).unwrap();
1032
1033 let source_group = destination_group.borrow().duplicate();
1034
1035 let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
1036 entry_set_field_and_commit(&entry, "Title", "entry1_updated_from_destination").unwrap();
1037
1038 let entry = with_node::<Group, _, _>(&source_group, |g| g.entries()[0].clone()).unwrap();
1039 entry_set_field_and_commit(&entry, "Title", "entry1_updated_from_source").unwrap();
1040
1041 let merge_result = Group::merge(&destination_group, &source_group).unwrap();
1042 assert_eq!(merge_result.warnings.len(), 0);
1043 assert_eq!(merge_result.events.len(), 1);
1044
1045 let entry = with_node::<Group, _, _>(&destination_group, |g| g.entries()[0].clone()).unwrap();
1046 assert_eq!(entry.borrow().get_title(), Some("entry1_updated_from_source"));
1047
1048 let merged_history = with_node::<Entry, _, _>(&entry, |e| e.history.clone().unwrap()).unwrap();
1049 assert!(merged_history.is_ordered());
1050 assert_eq!(merged_history.entries.len(), 3);
1051 let merged_entry = &merged_history.entries[1];
1052 assert_eq!(merged_entry.get_title(), Some("entry1_updated_from_destination"));
1053
1054 let destination_group_dup = destination_group.borrow().duplicate();
1056 let merge_result = Group::merge(&destination_group, &destination_group_dup).unwrap();
1057 assert_eq!(merge_result.warnings.len(), 0);
1058 assert_eq!(merge_result.events.len(), 0);
1059 }
1060
1061 #[test]
1062 fn get() {
1063 let db = Database::new(Default::default());
1064
1065 let general_group = rc_refcell_node(Group::new("General"));
1066 let sample_entry = rc_refcell_node(Entry::default());
1067 sample_entry.borrow_mut().set_title(Some("Sample Entry #2"));
1068 group_add_child(&general_group, sample_entry, 0).unwrap();
1069 group_add_child(&db.root, general_group, 0).unwrap();
1070
1071 assert!(Group::get(&db.root, &["General", "Sample Entry #2"]).is_some());
1072 assert!(Group::get(&db.root, &["General"]).is_some());
1073 assert!(Group::get(&db.root, &["Invalid Group"]).is_none());
1074 assert!(Group::get(&db.root, &[]).is_some());
1075 }
1076
1077 #[test]
1078 fn get_by_uuid() {
1079 let db = Database::new(Default::default());
1080
1081 let general_group = rc_refcell_node(Group::new("General"));
1082 let general_group_uuid = general_group.borrow().get_uuid().to_string();
1083 let sample_entry = rc_refcell_node(Entry::default());
1084 let sample_entry_uuid = sample_entry.borrow().get_uuid().to_string();
1085 sample_entry.borrow_mut().set_title(Some("Sample Entry #2"));
1086 group_add_child(&general_group, sample_entry, 0).unwrap();
1087 group_add_child(&db.root, general_group, 0).unwrap();
1088
1089 let invalid_uuid = uuid::Uuid::new_v4().to_string();
1090
1091 let group_path: [&str; 1] = [general_group_uuid.as_ref()];
1093 let entry_path: [&str; 2] = [general_group_uuid.as_ref(), sample_entry_uuid.as_ref()];
1094 let invalid_path: [&str; 1] = [invalid_uuid.as_ref()];
1095 let empty_path: [&str; 0] = [];
1096
1097 assert!(Group::get_by_uuid(&db.root, &group_path).is_some());
1098 assert!(Group::get_by_uuid(&db.root, &entry_path).is_some());
1099 assert!(Group::get_by_uuid(&db.root, &invalid_path).is_none());
1100 assert!(Group::get_by_uuid(&db.root, &empty_path).is_some());
1101
1102 let group_path = vec![general_group_uuid.clone()];
1104 let entry_path = vec![general_group_uuid.clone(), sample_entry_uuid.clone()];
1105 let invalid_path = vec![invalid_uuid.clone()];
1106 let empty_path: Vec<String> = vec![];
1107
1108 assert!(Group::get_by_uuid(&db.root, &group_path).is_some());
1109 assert!(Group::get_by_uuid(&db.root, &entry_path).is_some());
1110 assert!(Group::get_by_uuid(&db.root, &invalid_path).is_none());
1111 assert!(Group::get_by_uuid(&db.root, &empty_path).is_some());
1112 }
1113}