keepass_ng/db/
group.rs

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/// A database group with child groups and entries
27#[derive(Debug, Clone)]
28#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
29pub struct Group {
30    /// The unique identifier of the group
31    pub(crate) uuid: Uuid,
32
33    /// The name of the group
34    pub(crate) name: Option<String>,
35
36    /// Notes for the group
37    pub(crate) notes: Option<String>,
38
39    /// ID of the group's icon
40    pub(crate) icon_id: Option<IconId>,
41
42    /// UUID for a custom group icon
43    pub(crate) custom_icon_uuid: Option<Uuid>,
44
45    /// The list of child nodes (Groups or Entries)
46    pub(crate) children: Vec<SerializableNodePtr>,
47
48    /// The list of time fields for this group
49    pub(crate) times: Times,
50
51    // Custom Data
52    pub(crate) custom_data: CustomData,
53
54    /// Whether the group is expanded in the user interface
55    pub(crate) is_expanded: bool,
56
57    /// Default autotype sequence
58    pub(crate) default_autotype_sequence: Option<String>,
59
60    /// Whether autotype is enabled
61    // TODO: in example XML files, this is "null" - what should the type be?
62    pub(crate) enable_autotype: Option<String>,
63
64    /// Whether searching is enabled
65    // TODO: in example XML files, this is "null" - what should the type be?
66    pub(crate) enable_searching: Option<String>,
67
68    /// UUID for the last top visible entry
69    // TODO figure out what that is supposed to mean. According to the KeePass sourcecode, it has
70    // something to do with restoring selected items when re-opening a database.
71    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        // && self.parent == other.parent
113    }
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    /// Recursively get a Group or Entry reference by specifying a path relative to the current Group
228    /// ```
229    /// use keepass_ng::{
230    ///     db::{with_node, Database, Entry, Group},
231    ///     DatabaseKey,
232    /// };
233    /// use std::fs::File;
234    ///
235    /// let mut file = File::open("tests/resources/test_db_with_password.kdbx").unwrap();
236    /// let db = Database::open(&mut file, DatabaseKey::new().with_password("demopass")).unwrap();
237    ///
238    /// let e = Group::get(&db.root, &["General", "Sample Entry #2"]).unwrap();
239    /// with_node::<Entry, _, _>(&e, |e| {
240    ///     println!("User: {}", e.get_username().unwrap());
241    /// });
242    /// ```
243    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                    // current_location.push(id);
337                    return Some(current_location);
338                }
339            } else if node_is_group(node) {
340                if node_uuid == id {
341                    // current_location.push(id);
342                    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                // This should never happen.
382                // This means that a group was updated without updating the last modification
383                // timestamp.
384                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            // The location changed timestamp is handled separately when merging two databases.
398            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        // The group was not found, so we create it.
598        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    /// Merge this group with another group
607    #[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        // Handle entry relocation.
616        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        // Handle entry updates
658        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                        // This should never happen.
684                        // This means that an entry was updated without updating the last modification
685                        // timestamp.
686                        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                // merged_entry.borrow_mut().set_parent(existing_entry.borrow().get_parent());
700                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                // TODO should we update the time info for the entry?
714                log.events.push(MergeEvent {
715                    event_type: MergeEventType::EntryCreated,
716                    node_uuid: entry.borrow().get_uuid(),
717                });
718            }
719        }
720
721        // TODO handle deleted objects
722        Ok(log)
723    }
724
725    // Recursively get all the entries in the group, along with their
726    // location.
727    #[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            // The 2 groups should be exactly the same after merging, since
776            // nothing was performed during the merge.
777            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        // Merging twice in a row, even if the first merge updated the destination group,
794        // should not create more changes.
795        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        // Merging the same group again should not create a duplicate entry.
820        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        // FIXME we should not have to update the history here. We should
913        // have a better compare function in the merge function instead.
914        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            // FIXME we should not have to update the history here. We should
954            // have a better compare function in the merge function instead.
955            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        // Merging again should not result in any additional change.
1055        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        // Testing with references to the UUIDs
1092        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        // Testing with owned versions of the UUIDs.
1103        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}