1 // Copyright 2018-2019 Mozilla
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
16 collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
20 use crate::driver::{AbortSignal, DefaultAbortSignal, DefaultDriver, Driver};
21 use crate::error::{ErrorKind, Result};
22 use crate::guid::{Guid, IsValidGuid, TAGS_GUID};
23 use crate::tree::{Content, MergeState, MergedNode, Node, Tree, Validity};
25 /// Structure change types, used to indicate if a node on one side is moved
26 /// or deleted on the other.
27 #[derive(Eq, PartialEq)]
28 enum StructureChange {
29 /// Node not deleted, or doesn't exist, on the other side.
31 /// Node moved on the other side.
33 /// Node deleted on the other side.
37 /// Records structure change counters for telemetry.
38 #[derive(Clone, Copy, Default, Debug, Eq, Hash, PartialEq)]
39 pub struct StructureCounts {
40 /// Remote non-folder change wins over local deletion.
41 pub remote_revives: usize,
42 /// Local folder deletion wins over remote change.
43 pub local_deletes: usize,
44 /// Local non-folder change wins over remote deletion.
45 pub local_revives: usize,
46 /// Remote folder deletion wins over local change.
47 pub remote_deletes: usize,
48 /// Deduped local items.
50 /// Total number of nodes in the merged tree, excluding the
52 pub merged_nodes: usize,
55 /// Holds (matching remote dupes for local GUIDs, matching local dupes for
57 type MatchingDupes<'t> = (HashMap<Guid, Node<'t>>, HashMap<Guid, Node<'t>>);
59 /// Indicates which side to take in case of a merge conflict.
60 #[derive(Clone, Copy, Debug)]
61 enum ConflictResolution {
67 /// A hash key used to match dupes by content.
68 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
70 /// Matches a dupe by content only. Used for bookmarks, queries, folders,
72 WithoutPosition(&'a Content),
73 /// Matches a dupe by content and position. Used for separators.
74 WithPosition(&'a Content, usize),
77 /// A two-way merger that produces a complete merged tree from a complete local
78 /// tree and a complete remote tree with changes since the last sync.
80 /// This is ported almost directly from iOS. On iOS, the `ThreeWayMerger` takes
81 /// a complete "mirror" tree with the server state after the last sync, and two
82 /// incomplete trees with local and remote changes to the mirror: "local" and
83 /// "mirror", respectively. Overlaying buffer onto mirror yields the current
84 /// server tree; overlaying local onto mirror yields the complete local tree.
86 /// Dogear doesn't store the shared parent for changed items, so we can only
87 /// do two-way merges. Our local tree is the union of iOS's mirror and local,
88 /// and our remote tree is the union of iOS's mirror and buffer.
90 /// Unlike iOS, Dogear doesn't distinguish between structure and value changes.
91 /// The `needs_merge` flag notes *that* a bookmark changed, but not *how*. This
92 /// means we might detect conflicts, and revert changes on one side, for cases
93 /// that iOS can merge cleanly.
95 /// Fortunately, most of our users don't organize their bookmarks into deeply
96 /// nested hierarchies, or make conflicting changes on multiple devices
97 /// simultaneously. A simpler two-way tree merge strikes a good balance between
98 /// correctness and complexity.
99 pub struct Merger<'t, D = DefaultDriver, A = DefaultAbortSignal> {
102 local_tree: &'t Tree,
103 remote_tree: &'t Tree,
104 matching_dupes_by_local_parent_guid: HashMap<Guid, MatchingDupes<'t>>,
105 merged_guids: HashSet<Guid>,
106 delete_locally: HashSet<Guid>,
107 delete_remotely: HashSet<Guid>,
108 structure_counts: StructureCounts,
111 impl<'t> Merger<'t, DefaultDriver, DefaultAbortSignal> {
112 /// Creates a merger with the default merge driver.
113 pub fn new(local_tree: &'t Tree, remote_tree: &'t Tree) -> Merger<'t> {
115 driver: &DefaultDriver,
116 signal: &DefaultAbortSignal,
119 matching_dupes_by_local_parent_guid: HashMap::new(),
120 merged_guids: HashSet::new(),
121 delete_locally: HashSet::new(),
122 delete_remotely: HashSet::new(),
123 structure_counts: StructureCounts::default(),
128 impl<'t, D: Driver, A: AbortSignal> Merger<'t, D, A> {
129 /// Creates a merger with the given merge driver and contents.
133 local_tree: &'t Tree,
134 remote_tree: &'t Tree,
135 ) -> Merger<'t, D, A> {
141 matching_dupes_by_local_parent_guid: HashMap::new(),
142 merged_guids: HashSet::new(),
143 delete_locally: HashSet::new(),
144 delete_remotely: HashSet::new(),
145 structure_counts: StructureCounts::default(),
149 /// Builds a merged tree from the local and remote trees.
150 pub fn merge(mut self) -> Result<MergedRoot<'t>> {
151 let merged_root_node = {
152 let local_root_node = self.local_tree.root();
153 let remote_root_node = self.remote_tree.root();
154 self.two_way_merge(local_root_node, remote_root_node)?
157 // Any remaining deletions on one side should be deleted on the other side.
158 // This happens when the remote tree has tombstones for items that don't
159 // exist locally, or the local tree has tombstones for items that
160 // aren't on the server.
161 for guid in self.local_tree.deletions() {
162 self.signal.err_if_aborted()?;
163 if !self.mentions(guid) {
164 self.delete_remotely.insert(guid.clone());
167 for guid in self.remote_tree.deletions() {
168 self.signal.err_if_aborted()?;
169 if !self.mentions(guid) {
170 self.delete_locally.insert(guid.clone());
174 // The merged tree should know about all items mentioned in the local
175 // and remote trees. Otherwise, it's incomplete, and we can't apply it.
176 // This indicates a bug in the merger.
177 for guid in self.local_tree.guids() {
178 self.signal.err_if_aborted()?;
179 if !self.mentions(guid) {
180 return Err(ErrorKind::UnmergedLocalItems.into());
183 for guid in self.remote_tree.guids() {
184 self.signal.err_if_aborted()?;
185 if !self.mentions(guid) {
186 return Err(ErrorKind::UnmergedRemoteItems.into());
191 local_tree: self.local_tree,
192 remote_tree: self.remote_tree,
193 node: merged_root_node,
194 merged_guids: self.merged_guids,
195 delete_locally: self.delete_locally,
196 delete_remotely: self.delete_remotely,
197 structure_counts: self.structure_counts,
202 fn mentions(&self, guid: &Guid) -> bool {
203 self.merged_guids.contains(guid)
204 || self.delete_locally.contains(guid)
205 || self.delete_remotely.contains(guid)
208 fn merge_local_only_node(&mut self, local_node: Node<'t>) -> Result<MergedNode<'t>> {
209 trace!(self.driver, "Item {} only exists locally", local_node);
211 self.merged_guids.insert(local_node.guid.clone());
213 let merged_guid = if local_node.guid.is_valid_guid() {
214 local_node.guid.clone()
218 "Generating new GUID for local node {}", local_node
220 self.signal.err_if_aborted()?;
221 let new_guid = self.driver.generate_new_guid(&local_node.guid)?;
222 if new_guid != local_node.guid {
223 if self.merged_guids.contains(&new_guid) {
224 return Err(ErrorKind::DuplicateItem(new_guid).into());
226 self.merged_guids.insert(new_guid.clone());
231 let mut merged_node = MergedNode::new(merged_guid, MergeState::LocalOnly(local_node));
232 // The local folder doesn't exist remotely, but its children might, so
233 // we still need to recursively walk and merge them. This method will
234 // change the merge state from local to new if any children were moved
236 for local_child_node in local_node.children() {
237 self.signal.err_if_aborted()?;
238 self.merge_local_child_into_merged_node(
246 if local_node.diverged() {
247 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
253 fn merge_remote_only_node(&mut self, remote_node: Node<'t>) -> Result<MergedNode<'t>> {
254 trace!(self.driver, "Item {} only exists remotely", remote_node);
256 self.merged_guids.insert(remote_node.guid.clone());
258 let merged_guid = if remote_node.guid.is_valid_guid() {
259 remote_node.guid.clone()
263 "Generating new GUID for remote node {}", remote_node
265 self.signal.err_if_aborted()?;
266 let new_guid = self.driver.generate_new_guid(&remote_node.guid)?;
267 if new_guid != remote_node.guid {
268 if self.merged_guids.contains(&new_guid) {
269 return Err(ErrorKind::DuplicateItem(new_guid).into());
271 self.merged_guids.insert(new_guid.clone());
272 // Upload tombstones for changed remote GUIDs.
273 self.delete_remotely.insert(remote_node.guid.clone());
277 let mut merged_node = MergedNode::new(merged_guid, MergeState::RemoteOnly(remote_node));
278 // As above, a remote folder's children might still exist locally, so we
279 // need to merge them and update the merge state from remote to new if
280 // any children were moved or deleted.
281 for remote_child_node in remote_node.children() {
282 self.signal.err_if_aborted()?;
283 self.merge_remote_child_into_merged_node(
291 if remote_node.diverged()
292 || merged_node.remote_guid_changed()
293 || remote_node.validity != Validity::Valid
295 // If the remote structure diverged, the merged item's GUID changed,
296 // or the item isn't valid, flag it for reupload.
297 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
303 /// Merges two nodes that exist locally and remotely.
306 local_node: Node<'t>,
307 remote_node: Node<'t>,
308 ) -> Result<MergedNode<'t>> {
311 "Item exists locally as {} and remotely as {}",
316 if !local_node.has_compatible_kind(&remote_node) {
319 "Merging local {} and remote {} with different kinds", local_node, remote_node
321 return Err(ErrorKind::MismatchedItemKind(
322 local_node.item().clone(),
323 remote_node.item().clone(),
328 self.merged_guids.insert(local_node.guid.clone());
329 self.merged_guids.insert(remote_node.guid.clone());
331 let merged_guid = if remote_node.guid.is_valid_guid() {
332 remote_node.guid.clone()
336 "Generating new valid GUID for node {}", remote_node
338 self.signal.err_if_aborted()?;
339 let new_guid = self.driver.generate_new_guid(&remote_node.guid)?;
340 if new_guid != remote_node.guid {
341 if self.merged_guids.contains(&new_guid) {
342 return Err(ErrorKind::DuplicateItem(new_guid).into());
344 self.merged_guids.insert(new_guid.clone());
345 // Upload tombstones for changed remote GUIDs.
346 self.delete_remotely.insert(remote_node.guid.clone());
351 let (item, children) = self.resolve_value_conflict(local_node, remote_node);
353 let mut merged_node = MergedNode::new(
356 ConflictResolution::Local => MergeState::Local {
360 ConflictResolution::Remote => MergeState::Remote {
364 ConflictResolution::Unchanged => MergeState::Unchanged {
372 ConflictResolution::Local => {
373 for local_child_node in local_node.children() {
374 self.signal.err_if_aborted()?;
375 self.merge_local_child_into_merged_node(
382 for remote_child_node in remote_node.children() {
383 self.signal.err_if_aborted()?;
384 self.merge_remote_child_into_merged_node(
393 ConflictResolution::Remote => {
394 for remote_child_node in remote_node.children() {
395 self.signal.err_if_aborted()?;
396 self.merge_remote_child_into_merged_node(
403 for local_child_node in local_node.children() {
404 self.signal.err_if_aborted()?;
405 self.merge_local_child_into_merged_node(
414 ConflictResolution::Unchanged => {
415 // The children are the same, so we only need to merge one side.
416 for (local_child_node, remote_child_node) in
417 local_node.children().zip(remote_node.children())
419 self.signal.err_if_aborted()?;
420 self.merge_unchanged_child_into_merged_node(
431 if local_node.diverged() {
432 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
435 if remote_node.diverged() || remote_node.validity != Validity::Valid {
436 // Flag remotely diverged and invalid items for reupload.
437 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
443 /// Merges two nodes with the same parents and positions.
445 /// Unlike items that have been moved, or exist only on one side, unchanged
446 /// children can be merged directly.
447 fn merge_unchanged_child_into_merged_node(
449 merged_node: &mut MergedNode<'t>,
450 local_parent_node: Node<'t>,
451 local_child_node: Node<'t>,
452 remote_parent_node: Node<'t>,
453 remote_child_node: Node<'t>,
456 !self.merged_guids.contains(&local_child_node.guid),
457 "Unchanged local child shouldn't have been merged"
460 !self.merged_guids.contains(&remote_child_node.guid),
461 "Unchanged remote child shouldn't have been merged"
464 // Even though the child exists on both sides, it might still be
465 // non-syncable or invalid, so we need to check for structure
467 let local_structure_change = self.check_for_local_structure_change_of_remote_node(
472 let remote_structure_change = self.check_for_remote_structure_change_of_local_node(
477 match (local_structure_change, remote_structure_change) {
478 (StructureChange::Deleted, StructureChange::Deleted) => {
479 // The child is deleted on both sides. We'll need to reupload
480 // and apply a new structure.
481 merged_node.merge_state = merged_node
483 .with_new_local_structure()
484 .with_new_remote_structure();
486 (StructureChange::Deleted, _) => {
487 // The child is deleted locally, but not remotely, so we only
488 // need to reupload a new structure.
489 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
491 (_, StructureChange::Deleted) => {
492 // The child is deleted remotely, so we only need to apply a
493 // new local structure.
494 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
497 // The child exists on both sides, so merge it now. If the GUID
498 // changes because it's invalid, we'll need to reapply the
499 // child, and reupload the child and its parent.
500 let mut merged_child_node =
501 self.two_way_merge(local_child_node, remote_child_node)?;
502 if merged_child_node.local_guid_changed() {
503 merged_child_node.merge_state =
504 merged_child_node.merge_state.with_new_local_structure();
506 if merged_node.remote_guid_changed() {
507 // The merged parent's GUID changed; flag the child for
508 // reupload with a new `parentid`.
509 merged_child_node.merge_state =
510 merged_child_node.merge_state.with_new_remote_structure();
512 if merged_child_node.remote_guid_changed() {
513 // The merged child's GUID changed; flag the parent for
514 // reupload with new `children`.
515 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
517 merged_node.merged_children.push(merged_child_node);
518 self.structure_counts.merged_nodes += 1;
525 /// Merges a remote child node into a merged folder node. This handles the
528 /// - The remote child is locally deleted. We recursively move all of its
529 /// descendants that don't exist locally to the merged folder.
530 /// - The remote child doesn't exist locally, but has a content match in the
531 /// corresponding local folder. We dedupe the local child to the remote
533 /// - The remote child exists locally, but in a different folder. We compare
534 /// merge flags and timestamps to decide where to keep the child.
535 /// - The remote child exists locally, and in the same folder. We merge the
536 /// local and remote children.
538 /// This is the inverse of `merge_local_child_into_merged_node`.
539 fn merge_remote_child_into_merged_node(
541 merged_node: &mut MergedNode<'t>,
542 local_parent_node: Option<Node<'t>>,
543 remote_parent_node: Node<'t>,
544 remote_child_node: Node<'t>,
546 if self.merged_guids.contains(&remote_child_node.guid) {
549 "Remote child {} already seen in another folder and merged",
552 // Omitting a remote child that we already merged locally means we
553 // have a new remote structure.
554 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
560 "Merging remote child {} of {} into {}",
566 // Check if the remote child is locally deleted. and move all
567 // descendants that aren't also remotely deleted to the merged node.
568 // This handles the case where a user deletes a folder on this device,
569 // and adds a bookmark to the same folder on another device. We want to
570 // keep the folder deleted, but we also don't want to lose the new
571 // bookmark, so we move the bookmark to the deleted folder's parent.
572 if self.check_for_local_structure_change_of_remote_node(
576 )? == StructureChange::Deleted
578 // Flag the merged parent for reupload, since we deleted the
580 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
584 // The remote child isn't locally deleted. Does it exist in the local tree?
585 if let Some(local_child_node) = self.local_tree.node_for_guid(&remote_child_node.guid) {
586 // The remote child exists in the local tree. Did it move?
587 let local_parent_node = local_child_node
589 .expect("Can't merge existing remote child without local parent");
593 "Remote child {} exists locally in {} and remotely in {}",
599 if self.remote_tree.is_deleted(&local_parent_node.guid) {
602 "Unconditionally taking remote move for {} to {} because local parent {} is \
609 let mut merged_child_node =
610 self.two_way_merge(local_child_node, remote_child_node)?;
611 merged_child_node.merge_state =
612 merged_child_node.merge_state.with_new_local_structure();
613 if merged_node.remote_guid_changed() {
614 // If the parent's GUID changed, flag the child for reupload, so that
615 // its `parentid` is correct.
616 merged_child_node.merge_state =
617 merged_child_node.merge_state.with_new_remote_structure();
619 if merged_child_node.remote_guid_changed() {
620 // If the child's GUID changed, flag the parent for reupload, so that
621 // its `children` are correct.
622 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
624 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
625 merged_node.merged_children.push(merged_child_node);
626 self.structure_counts.merged_nodes += 1;
630 match self.resolve_structure_conflict(
636 ConflictResolution::Local => {
637 // The local move is newer, so we ignore the remote move.
638 // We'll merge the remote child later, when we walk its new
642 "Remote child {} moved locally to {} and remotely to {}; \
643 keeping child in newer local parent and position",
649 // Flag the old parent for reupload, since we're moving
650 // the remote child. Note that, since we only flag the
651 // remote parent here, we don't need to handle
652 // reparenting and repositioning separately.
653 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
656 ConflictResolution::Remote | ConflictResolution::Unchanged => {
657 // The remote move is newer, so we merge the remote
658 // child now and ignore the local move.
659 let mut merged_child_node = if local_parent_node.guid != remote_parent_node.guid
663 "Remote child {} reparented locally to {} and remotely to {}; \
664 keeping child in newer remote parent",
669 let mut merged_child_node =
670 self.two_way_merge(local_child_node, remote_child_node)?;
671 merged_child_node.merge_state =
672 merged_child_node.merge_state.with_new_local_structure();
677 "Remote child {} repositioned locally in {} and remotely in {}; \
678 keeping child in newer remote position",
683 self.two_way_merge(local_child_node, remote_child_node)?
685 if merged_child_node.local_guid_changed() {
686 merged_child_node.merge_state =
687 merged_child_node.merge_state.with_new_local_structure();
689 if merged_node.remote_guid_changed() {
690 // The merged parent's GUID changed; flag the child for
691 // reupload with a new `parentid`.
692 merged_child_node.merge_state =
693 merged_child_node.merge_state.with_new_remote_structure();
695 if merged_child_node.remote_guid_changed() {
696 // The merged child's GUID changed; flag the parent for
697 // reupload with new `children`.
698 merged_node.merge_state =
699 merged_node.merge_state.with_new_remote_structure();
701 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
702 merged_node.merged_children.push(merged_child_node);
703 self.structure_counts.merged_nodes += 1;
710 // Remote child is not a root, and doesn't exist locally. Try to find a
711 // content match in the containing folder, and dedupe the local item if
715 "Remote child {} doesn't exist locally; looking for local content match",
719 let mut merged_child_node = if let Some(local_child_node_by_content) = self
720 .find_local_node_matching_remote_node(
726 self.two_way_merge(local_child_node_by_content, remote_child_node)
728 self.merge_remote_only_node(remote_child_node)
730 if merged_child_node.local_guid_changed() {
731 merged_child_node.merge_state =
732 merged_child_node.merge_state.with_new_local_structure();
734 if merged_node.remote_guid_changed() {
735 merged_child_node.merge_state =
736 merged_child_node.merge_state.with_new_remote_structure();
738 if merged_child_node.remote_guid_changed() {
739 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
741 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
742 merged_node.merged_children.push(merged_child_node);
743 self.structure_counts.merged_nodes += 1;
747 /// Merges a local child node into a merged folder node.
749 /// This is the inverse of `merge_remote_child_into_merged_node`.
750 fn merge_local_child_into_merged_node(
752 merged_node: &mut MergedNode<'t>,
753 local_parent_node: Node<'t>,
754 remote_parent_node: Option<Node<'t>>,
755 local_child_node: Node<'t>,
757 if self.merged_guids.contains(&local_child_node.guid) {
758 // We already merged the child when we walked another folder. Since
759 // a tree can't have duplicate GUIDs, we must have merged the remote
760 // child, so we have a new local structure.
763 "Local child {} already seen in another folder and merged",
766 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
772 "Merging local child {} of {} into {}",
778 // Check if the local child is remotely deleted, and move any new local
779 // descendants to the merged node if so.
780 if self.check_for_remote_structure_change_of_local_node(
784 )? == StructureChange::Deleted
786 // Since we're merging local nodes, we don't need to flag the merged
787 // parent for reupload.
788 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
792 // At this point, we know the local child isn't deleted. See if it
793 // exists in the remote tree.
794 if let Some(remote_child_node) = self.remote_tree.node_for_guid(&local_child_node.guid) {
795 // The local child exists remotely. It must have moved; otherwise, we
796 // would have seen it when we walked the remote children.
797 let remote_parent_node = remote_child_node
799 .expect("Can't merge existing local child without remote parent");
803 "Local child {} exists locally in {} and remotely in {}",
809 if self.local_tree.is_deleted(&remote_parent_node.guid) {
812 "Unconditionally taking local move for {} to {} because remote parent {} is \
819 // Merge and flag the new parent *and the locally moved child* for
820 // reupload. The parent references the child in its `children`; the
821 // child points back to the parent in its `parentid`.
823 // Reuploading the child isn't necessary for newer Desktops, which
824 // ignore the child's `parentid` and use the parent's `children`.
826 // However, older Desktop and Android use the child's `parentid` as
827 // canonical, while iOS is stricter and requires both to match.
828 let mut merged_child_node =
829 self.two_way_merge(local_child_node, remote_child_node)?;
830 if merged_child_node.local_guid_changed() {
831 merged_child_node.merge_state =
832 merged_child_node.merge_state.with_new_local_structure();
834 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
835 merged_child_node.merge_state =
836 merged_child_node.merge_state.with_new_remote_structure();
837 merged_node.merged_children.push(merged_child_node);
838 self.structure_counts.merged_nodes += 1;
842 match self.resolve_structure_conflict(
848 ConflictResolution::Local => {
849 // The local move is newer, so we merge the local child now
850 // and ignore the remote move.
851 if local_parent_node.guid != remote_parent_node.guid {
852 // The child moved to a different folder.
855 "Local child {} reparented locally to {} and remotely to {}; \
856 keeping child in newer local parent",
862 // Merge and flag both the new parent and child for
863 // reupload. See above for why.
864 let mut merged_child_node =
865 self.two_way_merge(local_child_node, remote_child_node)?;
866 if merged_child_node.local_guid_changed() {
867 merged_child_node.merge_state =
868 merged_child_node.merge_state.with_new_local_structure();
870 merged_node.merge_state =
871 merged_node.merge_state.with_new_remote_structure();
872 merged_child_node.merge_state =
873 merged_child_node.merge_state.with_new_remote_structure();
874 merged_node.merged_children.push(merged_child_node);
875 self.structure_counts.merged_nodes += 1;
879 "Local child {} repositioned locally in {} and remotely in {}; \
880 keeping child in newer local position",
886 // For position changes in the same folder, we only need to
887 // merge and flag the parent for reupload...
888 let mut merged_child_node =
889 self.two_way_merge(local_child_node, remote_child_node)?;
890 if merged_child_node.local_guid_changed() {
891 merged_child_node.merge_state =
892 merged_child_node.merge_state.with_new_local_structure();
894 merged_node.merge_state =
895 merged_node.merge_state.with_new_remote_structure();
896 if merged_node.remote_guid_changed() {
897 // ...Unless the merged parent's GUID also changed,
898 // in which case we also need to flag the
899 // repositioned child for reupload, so that its
900 // `parentid` is correct.
901 merged_child_node.merge_state =
902 merged_child_node.merge_state.with_new_remote_structure();
905 merged_node.merged_children.push(merged_child_node);
906 self.structure_counts.merged_nodes += 1;
910 ConflictResolution::Remote | ConflictResolution::Unchanged => {
911 // The remote move is newer, so we ignore the local
912 // move. We'll merge the local child later, when we
913 // walk its new remote parent.
914 if local_parent_node.guid != remote_parent_node.guid {
917 "Local child {} reparented locally to {} and remotely to {}; \
918 keeping child in newer remote parent",
926 "Local child {} repositioned locally in {} and remotely in {}; \
927 keeping child in newer remote position",
933 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
940 // Local child is not a root, and doesn't exist remotely. Try to find a
941 // content match in the containing folder, and dedupe the local item if
945 "Local child {} doesn't exist remotely; looking for remote content match",
949 let merged_child_node = if let Some(remote_child_node_by_content) = self
950 .find_remote_node_matching_local_node(
956 // The local child has a remote content match, so take the remote GUID
958 let mut merged_child_node =
959 self.two_way_merge(local_child_node, remote_child_node_by_content)?;
960 if merged_child_node.local_guid_changed() {
961 merged_child_node.merge_state =
962 merged_child_node.merge_state.with_new_local_structure();
964 if merged_node.remote_guid_changed() {
965 merged_child_node.merge_state =
966 merged_child_node.merge_state.with_new_remote_structure();
968 if merged_child_node.remote_guid_changed() {
969 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
971 merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
974 // The local child doesn't exist remotely, so flag the merged parent and
975 // new child for upload, and walk its descendants.
976 let mut merged_child_node = self.merge_local_only_node(local_child_node)?;
977 if merged_child_node.local_guid_changed() {
978 merged_child_node.merge_state =
979 merged_child_node.merge_state.with_new_local_structure();
981 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
982 merged_child_node.merge_state =
983 merged_child_node.merge_state.with_new_remote_structure();
986 merged_node.merged_children.push(merged_child_node);
987 self.structure_counts.merged_nodes += 1;
991 /// Determines which side to prefer, and which children to merge first,
992 /// for an item that exists on both sides.
993 fn resolve_value_conflict(
995 local_node: Node<'t>,
996 remote_node: Node<'t>,
997 ) -> (ConflictResolution, ConflictResolution) {
998 if remote_node.is_root() {
999 // Don't touch the Places root; it's not synced, anyway.
1000 return (ConflictResolution::Unchanged, ConflictResolution::Local);
1003 match (local_node.needs_merge, remote_node.needs_merge) {
1005 // The item changed locally and remotely.
1006 let item = if local_node.is_built_in_root() {
1007 // For roots, we always prefer the local side for item
1008 // changes, like the title (bug 1432614).
1009 ConflictResolution::Local
1011 // For other items, we check the validity to decide
1012 // which side to take.
1013 match (local_node.validity, remote_node.validity) {
1014 // If both are invalid, it doesn't matter which side
1015 // we pick; the item will be deleted, anyway.
1016 (Validity::Replace, Validity::Replace) => ConflictResolution::Unchanged,
1017 // If only one side is invalid, pick the other side.
1018 // This loses changes from that side, but we can't
1019 // apply or upload those changes, anyway.
1020 (Validity::Replace, _) => ConflictResolution::Remote,
1021 (_, Validity::Replace) => ConflictResolution::Local,
1023 // Otherwise, the item is either valid, or valid
1024 // but needs to be reuploaded or reapplied, so
1025 // compare timestamps to decide which side is newer.
1026 if local_node.age < remote_node.age {
1027 ConflictResolution::Local
1029 ConflictResolution::Remote
1034 // For children, it's easier: we always use the newer side, even
1035 // if we're taking local changes for the item.
1036 let children = if local_node.has_matching_children(remote_node) {
1037 ConflictResolution::Unchanged
1038 } else if local_node.age < remote_node.age {
1039 // The local change is newer, so merge local children first,
1040 // followed by remaining unmerged remote children.
1041 ConflictResolution::Local
1043 // The remote change is newer, so walk and merge remote
1044 // children first, then remaining local children.
1045 ConflictResolution::Remote
1051 // The item changed locally, but not remotely. Prefer the local
1052 // item, then merge local children first, followed by remote
1054 let item = match local_node.validity {
1055 Validity::Valid | Validity::Reupload => ConflictResolution::Local,
1056 Validity::Replace => ConflictResolution::Remote,
1058 let children = if local_node.has_matching_children(remote_node) {
1059 ConflictResolution::Unchanged
1061 ConflictResolution::Local
1067 // The item changed remotely, but not locally.
1068 let item = if local_node.is_built_in_root() {
1069 // For roots, we ignore remote item changes.
1070 ConflictResolution::Unchanged
1072 match remote_node.validity {
1073 Validity::Valid | Validity::Reupload => ConflictResolution::Remote,
1074 // And, for invalid remote items, we must reupload the
1075 // local side. This _loses remote changes_, but we can't
1076 // apply those changes, anyway.
1077 Validity::Replace => ConflictResolution::Local,
1080 let children = if local_node.has_matching_children(remote_node) {
1081 ConflictResolution::Unchanged
1083 ConflictResolution::Remote
1085 // For children, we always use the remote side.
1090 let item = match (local_node.validity, remote_node.validity) {
1091 (Validity::Replace, Validity::Replace) => ConflictResolution::Unchanged,
1092 (_, Validity::Replace) => ConflictResolution::Local,
1093 (Validity::Replace, _) => ConflictResolution::Remote,
1094 (_, _) => ConflictResolution::Unchanged,
1096 // If the child lists are identical, the structure is unchanged.
1097 // Otherwise, the children differ even though the items aren't
1098 // flagged as unmerged, so we prefer the newer side.
1099 let children = if local_node.has_matching_children(remote_node) {
1100 ConflictResolution::Unchanged
1101 } else if local_node.age < remote_node.age {
1102 ConflictResolution::Local
1104 ConflictResolution::Remote
1111 /// Determines where to keep a child of a folder that exists on both sides.
1112 fn resolve_structure_conflict(
1114 local_parent_node: Node<'t>,
1115 local_child_node: Node<'t>,
1116 remote_parent_node: Node<'t>,
1117 remote_child_node: Node<'t>,
1118 ) -> ConflictResolution {
1119 if remote_child_node.is_built_in_root() {
1120 // Always use the local parent and position for roots.
1121 return ConflictResolution::Local;
1125 local_parent_node.needs_merge,
1126 remote_parent_node.needs_merge,
1129 // If both parents changed, compare timestamps to decide where
1130 // to keep the local child.
1131 let latest_local_age = local_child_node.age.min(local_parent_node.age);
1132 let latest_remote_age = remote_child_node.age.min(remote_parent_node.age);
1134 if latest_local_age < latest_remote_age {
1135 ConflictResolution::Local
1137 ConflictResolution::Remote
1141 // If only the local or remote parent changed, keep the child in its
1143 (true, false) => ConflictResolution::Local,
1144 (false, true) => ConflictResolution::Remote,
1146 (false, false) => ConflictResolution::Unchanged,
1150 /// Checks if a remote node is locally moved or deleted, and reparents any
1151 /// descendants that aren't also remotely deleted to the merged node.
1153 /// This is the inverse of
1154 /// `check_for_remote_structure_change_of_local_node`.
1155 fn check_for_local_structure_change_of_remote_node(
1157 merged_node: &mut MergedNode<'t>,
1158 remote_parent_node: Node<'t>,
1159 remote_node: Node<'t>,
1160 ) -> Result<StructureChange> {
1161 if !remote_node.is_syncable() {
1162 // If the remote node is known to be non-syncable, we unconditionally
1163 // delete it, even if it's syncable or moved locally.
1166 "Deleting non-syncable remote node {}",
1169 return self.delete_remote_node(merged_node, remote_node);
1172 if !self.local_tree.is_deleted(&remote_node.guid) {
1173 if let Some(local_node) = self.local_tree.node_for_guid(&remote_node.guid) {
1174 if !local_node.is_syncable() {
1175 // The remote node is syncable, but the local node is
1176 // non-syncable. Unconditionally delete it.
1179 "Remote node {} is syncable, but local node {} isn't; deleting",
1183 return self.delete_remote_node(merged_node, remote_node);
1185 if local_node.validity == Validity::Replace
1186 && remote_node.validity == Validity::Replace
1188 // The nodes are invalid on both sides, so we can't apply
1189 // or reupload a valid copy. Delete it.
1190 return self.delete_remote_node(merged_node, remote_node);
1192 let local_parent_node = local_node
1194 .expect("Can't check for structure changes without local parent");
1195 if local_parent_node.guid != remote_parent_node.guid {
1196 return Ok(StructureChange::Moved);
1198 return Ok(StructureChange::Unchanged);
1200 if remote_node.validity == Validity::Replace {
1201 // The remote node is invalid and doesn't exist locally, so we
1202 // can't reupload a valid copy. We must delete it.
1203 return self.delete_remote_node(merged_node, remote_node);
1205 return Ok(StructureChange::Unchanged);
1208 if remote_node.validity == Validity::Replace {
1209 // The remote node is invalid and deleted locally, so we can't
1210 // reupload a valid copy. Delete it.
1211 return self.delete_remote_node(merged_node, remote_node);
1214 if remote_node.is_built_in_root() {
1215 // If the remote node is a content root, don't delete it locally.
1216 return Ok(StructureChange::Unchanged);
1219 if remote_node.needs_merge {
1220 if !remote_node.is_folder() {
1221 // If a non-folder child is deleted locally and changed remotely, we
1222 // ignore the local deletion and take the remote child.
1225 "Remote non-folder {} deleted locally and changed remotely; \
1226 taking remote change",
1229 self.structure_counts.remote_revives += 1;
1230 return Ok(StructureChange::Unchanged);
1232 // For folders, we always take the local deletion and relocate remotely
1233 // changed grandchildren to the merged node. We could use the remote
1234 // tree to revive the child folder, but it's easier to relocate orphaned
1235 // grandchildren than to partially revive the child folder.
1238 "Remote folder {} deleted locally and changed remotely; \
1239 taking local deletion",
1242 self.structure_counts.local_deletes += 1;
1246 "Remote node {} deleted locally and not changed remotely; \
1247 taking local deletion",
1252 // Take the local deletion and relocate any new remote descendants to the
1254 self.delete_remote_node(merged_node, remote_node)
1257 /// Checks if a local node is remotely moved or deleted, and reparents any
1258 /// descendants that aren't also locally deleted to the merged node.
1260 /// This is the inverse of
1261 /// `check_for_local_structure_change_of_remote_node`.
1262 fn check_for_remote_structure_change_of_local_node(
1264 merged_node: &mut MergedNode<'t>,
1265 local_parent_node: Node<'t>,
1266 local_node: Node<'t>,
1267 ) -> Result<StructureChange> {
1268 if !local_node.is_syncable() {
1269 // If the local node is known to be non-syncable, we unconditionally
1270 // delete it, even if it's syncable or moved remotely.
1273 "Deleting non-syncable local node {}",
1276 return self.delete_local_node(merged_node, local_node);
1279 if !self.remote_tree.is_deleted(&local_node.guid) {
1280 if let Some(remote_node) = self.remote_tree.node_for_guid(&local_node.guid) {
1281 if !remote_node.is_syncable() {
1282 // The local node is syncable, but the remote node is not.
1283 // This can happen if we applied an orphaned left pane
1284 // query in a previous sync, and later saw the left pane
1285 // root on the server. Since we now have the complete
1286 // subtree, we can remove it.
1289 "Local node {} is syncable, but remote node {} isn't; deleting",
1293 return self.delete_local_node(merged_node, local_node);
1295 if remote_node.validity == Validity::Replace
1296 && local_node.validity == Validity::Replace
1298 // The nodes are invalid on both sides, so we can't replace
1299 // the local copy with a remote one. Delete it.
1300 return self.delete_local_node(merged_node, local_node);
1302 // Otherwise, either both nodes are valid; or the remote node
1303 // is invalid but the local node is valid, so we can reupload a
1305 let remote_parent_node = remote_node
1307 .expect("Can't check for structure changes without remote parent");
1308 if remote_parent_node.guid != local_parent_node.guid {
1309 return Ok(StructureChange::Moved);
1311 return Ok(StructureChange::Unchanged);
1313 if local_node.validity == Validity::Replace {
1314 // The local node is invalid and doesn't exist remotely, so
1315 // we can't replace the local copy. Delete it.
1316 return self.delete_local_node(merged_node, local_node);
1318 return Ok(StructureChange::Unchanged);
1321 if local_node.validity == Validity::Replace {
1322 // The local node is invalid and deleted remotely, so we can't
1323 // replace the local copy. Delete it.
1324 return self.delete_local_node(merged_node, local_node);
1327 if local_node.is_built_in_root() {
1328 // If the local node is a content root, don't delete it remotely.
1329 return Ok(StructureChange::Unchanged);
1332 // See `check_for_local_structure_change_of_remote_node` for an
1333 // explanation of how we decide to take or ignore a deletion.
1334 if local_node.needs_merge {
1335 if !local_node.is_folder() {
1338 "Local non-folder {} deleted remotely and changed locally; taking local change",
1341 self.structure_counts.local_revives += 1;
1342 return Ok(StructureChange::Unchanged);
1346 "Local folder {} deleted remotely and changed locally; taking remote deletion",
1349 self.structure_counts.remote_deletes += 1;
1353 "Local node {} deleted remotely and not changed locally; taking remote deletion",
1358 // Take the remote deletion and relocate any new local descendants to the
1360 self.delete_local_node(merged_node, local_node)
1363 /// Marks a remote node as deleted, and relocates all remote descendants
1364 /// that aren't also locally deleted to the merged node. This avoids data
1365 /// loss if the user adds a bookmark to a folder on another device, and
1366 /// deletes that folder locally.
1368 /// This is the inverse of `delete_local_node`.
1369 fn delete_remote_node(
1371 merged_node: &mut MergedNode<'t>,
1372 remote_node: Node<'t>,
1373 ) -> Result<StructureChange> {
1374 self.delete_remotely.insert(remote_node.guid.clone());
1375 for remote_child_node in remote_node.children() {
1376 self.signal.err_if_aborted()?;
1377 if self.merged_guids.contains(&remote_child_node.guid) {
1380 "Remote child {} can't be an orphan; already merged",
1385 match self.check_for_local_structure_change_of_remote_node(
1390 StructureChange::Moved | StructureChange::Deleted => {
1391 // The remote child is already moved or deleted locally, so we should
1392 // ignore it instead of treating it as a remote orphan.
1395 StructureChange::Unchanged => {
1398 "Relocating remote orphan {} to {}",
1403 // Flag the new parent and moved remote orphan for reupload.
1404 let mut merged_orphan_node = if let Some(local_child_node) =
1405 self.local_tree.node_for_guid(&remote_child_node.guid)
1407 self.two_way_merge(local_child_node, remote_child_node)
1409 self.merge_remote_only_node(remote_child_node)
1411 merged_node.merge_state = merged_node
1413 .with_new_local_structure()
1414 .with_new_remote_structure();
1415 merged_orphan_node.merge_state = merged_orphan_node
1417 .with_new_local_structure()
1418 .with_new_remote_structure();
1419 merged_node.merged_children.push(merged_orphan_node);
1420 self.structure_counts.merged_nodes += 1;
1424 Ok(StructureChange::Deleted)
1427 /// Marks a local node as deleted, and relocates all local descendants
1428 /// that aren't also remotely deleted to the merged node.
1430 /// This is the inverse of `delete_remote_node`.
1431 fn delete_local_node(
1433 merged_node: &mut MergedNode<'t>,
1434 local_node: Node<'t>,
1435 ) -> Result<StructureChange> {
1436 self.delete_locally.insert(local_node.guid.clone());
1437 for local_child_node in local_node.children() {
1438 self.signal.err_if_aborted()?;
1439 if self.merged_guids.contains(&local_child_node.guid) {
1442 "Local child {} can't be an orphan; already merged",
1447 match self.check_for_remote_structure_change_of_local_node(
1452 StructureChange::Moved | StructureChange::Deleted => {
1453 // The local child is already moved or deleted remotely, so we should
1454 // ignore it instead of treating it as a local orphan.
1457 StructureChange::Unchanged => {
1460 "Relocating local orphan {} to {}",
1465 // Flag the new parent and moved local orphan for reupload.
1466 let mut merged_orphan_node = if let Some(remote_child_node) =
1467 self.remote_tree.node_for_guid(&local_child_node.guid)
1469 self.two_way_merge(local_child_node, remote_child_node)
1471 self.merge_local_only_node(local_child_node)
1473 merged_node.merge_state = merged_node
1475 .with_new_local_structure()
1476 .with_new_remote_structure();
1477 merged_orphan_node.merge_state = merged_orphan_node
1479 .with_new_local_structure()
1480 .with_new_remote_structure();
1481 merged_node.merged_children.push(merged_orphan_node);
1482 self.structure_counts.merged_nodes += 1;
1486 Ok(StructureChange::Deleted)
1489 /// Finds all children of a local folder with similar content as children of
1490 /// the corresponding remote folder. This is used to dedupe local items that
1491 /// haven't been uploaded yet, to remote items that don't exist locally.
1493 /// Recall that we match items by GUID as we walk down the tree. If a GUID
1494 /// on one side doesn't exist on the other, we fall back to a content
1495 /// match in the same folder.
1497 /// This method is called the first time that
1498 /// `find_remote_node_matching_local_node` merges a local child that
1499 /// doesn't exist remotely, and
1500 /// the first time that `find_local_node_matching_remote_node` merges a
1501 /// remote child that doesn't exist locally.
1503 /// Finding all possible dupes is O(m + n) in the worst case, where `m` is
1504 /// the number of local children, and `n` is the number of remote
1505 /// children. We cache matches in
1506 /// `matching_dupes_by_local_parent_guid`, so deduping all
1507 /// remaining children of the same folder, on both sides, only needs two
1508 /// O(1) map lookups per child.
1509 fn find_all_matching_dupes_in_folders(
1511 local_parent_node: Node<'t>,
1512 remote_parent_node: Node<'t>,
1513 ) -> Result<MatchingDupes<'t>> {
1514 let mut dupe_key_to_local_nodes: HashMap<DupeKey<'_>, VecDeque<_>> = HashMap::new();
1516 for (local_position, local_child_node) in local_parent_node.children().enumerate() {
1517 self.signal.err_if_aborted()?;
1518 if local_child_node.is_built_in_root() {
1521 "Not deduping local built-in root {}",
1526 if self.remote_tree.mentions(&local_child_node.guid) {
1529 "Not deduping local child {}; already deleted or exists remotely",
1534 match local_child_node.content() {
1535 Some(local_child_content) => {
1536 // Store matching local children in an array, in case multiple children
1537 // have the same dupe key (for example, a toolbar containing multiple
1538 // empty folders, as in bug 1213369).
1539 let dupe_key = match local_child_content {
1540 Content::Bookmark { .. } | Content::Folder { .. } => {
1541 DupeKey::WithoutPosition(local_child_content)
1543 Content::Separator => {
1544 DupeKey::WithPosition(local_child_content, local_position)
1547 let local_nodes_for_key = dupe_key_to_local_nodes.entry(dupe_key).or_default();
1548 local_nodes_for_key.push_back(local_child_node);
1553 "Not deduping local child {} without content info",
1560 let mut local_to_remote = HashMap::new();
1561 let mut remote_to_local = HashMap::new();
1563 for (remote_position, remote_child_node) in remote_parent_node.children().enumerate() {
1564 self.signal.err_if_aborted()?;
1565 if remote_child_node.is_built_in_root() {
1568 "Not deduping remote built-in root {}",
1573 if self.local_tree.mentions(&remote_child_node.guid) {
1576 "Not deduping remote child {}; already deleted or exists locally",
1581 if remote_to_local.contains_key(&remote_child_node.guid) {
1584 "Not deduping remote child {}; already deduped",
1589 // Note that we don't need to check if the remote node is deleted
1590 // locally, because it wouldn't have local content entries if it
1592 match remote_child_node.content() {
1593 Some(remote_child_content) => {
1594 let dupe_key = match remote_child_content {
1595 Content::Bookmark { .. } | Content::Folder { .. } => {
1596 DupeKey::WithoutPosition(remote_child_content)
1598 Content::Separator => {
1599 DupeKey::WithPosition(remote_child_content, remote_position)
1602 if let Some(local_nodes_for_key) = dupe_key_to_local_nodes.get_mut(&dupe_key) {
1603 if let Some(local_child_node) = local_nodes_for_key.pop_front() {
1606 "Deduping local child {} to remote child {}",
1611 .insert(local_child_node.guid.clone(), remote_child_node);
1613 .insert(remote_child_node.guid.clone(), local_child_node);
1617 "Not deduping remote child {}; no remaining local content matches",
1625 "Not deduping remote child {}; no local content matches",
1634 "Not deduping remote child {} without content info",
1641 Ok((local_to_remote, remote_to_local))
1644 /// Finds a remote node with a different GUID that matches the content of a
1647 /// This is the inverse of `find_local_node_matching_remote_node`.
1648 fn find_remote_node_matching_local_node(
1650 merged_node: &MergedNode<'t>,
1651 local_parent_node: Node<'t>,
1652 remote_parent_node: Option<Node<'t>>,
1653 local_child_node: Node<'t>,
1654 ) -> Result<Option<Node<'t>>> {
1655 if let Some(remote_parent_node) = remote_parent_node {
1656 let mut matching_dupes_by_local_parent_guid = mem::replace(
1657 &mut self.matching_dupes_by_local_parent_guid,
1660 let new_remote_node = {
1661 let (local_to_remote, _) = match matching_dupes_by_local_parent_guid
1662 .entry(local_parent_node.guid.clone())
1664 Entry::Occupied(entry) => entry.into_mut(),
1665 Entry::Vacant(entry) => {
1668 "First local child {} doesn't exist remotely; \
1669 finding all matching dupes in local {} and remote {}",
1674 let matching_dupes = self.find_all_matching_dupes_in_folders(
1678 entry.insert(matching_dupes)
1681 let new_remote_node = local_to_remote.get(&local_child_node.guid);
1682 new_remote_node.map(|node| {
1683 self.structure_counts.dupes += 1;
1687 self.matching_dupes_by_local_parent_guid = matching_dupes_by_local_parent_guid;
1692 "Merged node {} doesn't exist remotely; no potential dupes for local child {}",
1700 /// Finds a local node with a different GUID that matches the content of a
1703 /// This is the inverse of `find_remote_node_matching_local_node`.
1704 fn find_local_node_matching_remote_node(
1706 merged_node: &MergedNode<'t>,
1707 local_parent_node: Option<Node<'t>>,
1708 remote_parent_node: Node<'t>,
1709 remote_child_node: Node<'t>,
1710 ) -> Result<Option<Node<'t>>> {
1711 if let Some(local_parent_node) = local_parent_node {
1712 let mut matching_dupes_by_local_parent_guid = mem::replace(
1713 &mut self.matching_dupes_by_local_parent_guid,
1716 let new_local_node = {
1717 let (_, remote_to_local) = match matching_dupes_by_local_parent_guid
1718 .entry(local_parent_node.guid.clone())
1720 Entry::Occupied(entry) => entry.into_mut(),
1721 Entry::Vacant(entry) => {
1724 "First remote child {} doesn't exist locally; \
1725 finding all matching dupes in local {} and remote {}",
1730 let matching_dupes = self.find_all_matching_dupes_in_folders(
1734 entry.insert(matching_dupes)
1737 let new_local_node = remote_to_local.get(&remote_child_node.guid);
1738 new_local_node.map(|node| {
1739 self.structure_counts.dupes += 1;
1743 self.matching_dupes_by_local_parent_guid = matching_dupes_by_local_parent_guid;
1748 "Merged node {} doesn't exist locally; no potential dupes for remote child {}",
1757 /// The root of a merged tree, from which all merged nodes descend.
1759 pub struct MergedRoot<'t> {
1760 local_tree: &'t Tree,
1761 remote_tree: &'t Tree,
1762 node: MergedNode<'t>,
1763 merged_guids: HashSet<Guid>,
1764 delete_locally: HashSet<Guid>,
1765 delete_remotely: HashSet<Guid>,
1766 structure_counts: StructureCounts,
1769 impl<'t> MergedRoot<'t> {
1770 /// Returns the root node.
1772 pub fn node(&self) -> &MergedNode<'_> {
1776 /// Returns a sequence of completion operations, or "completion ops", to
1777 /// apply to the local tree so that it matches the merged tree. The abort
1778 /// signal can be used to interrupt fetching the ops.
1779 pub fn completion_ops_with_signal(
1781 signal: &impl AbortSignal,
1782 ) -> Result<CompletionOps<'_>> {
1783 let mut ops = CompletionOps::default();
1784 accumulate(signal, &mut ops, self.node(), 1, false)?;
1786 // Clean up tombstones for local and remote items that are revived on
1791 .difference(&self.delete_remotely)
1793 // For ignored local deletions, we remove the local tombstone. If
1794 // the item is already deleted remotely, we also flag the remote
1795 // tombstone as merged.
1796 signal.err_if_aborted()?;
1797 ops.delete_local_tombstones.push(DeleteLocalTombstone(guid));
1798 if self.remote_tree.is_deleted(guid) {
1799 ops.set_remote_merged.push(SetRemoteMerged(guid));
1805 .difference(&self.delete_locally)
1806 .filter(|guid| !self.local_tree.exists(guid))
1808 // Ignored remote deletions are handled a little differently. Unlike
1809 // local tombstones, which are stored separately from items, remote
1810 // tombstones and items are stored in the same table. This means we
1811 // only need to flag the remote tombstone as merged if it's for an
1812 // item that doesn't exist locally. If the local item does exist,
1813 // we can avoid an extra write to flag the tombstone that we'll
1814 // replace with the item, anyway. If the item is already deleted
1815 // locally, we also delete the local tombstone.
1816 signal.err_if_aborted()?;
1817 ops.set_remote_merged.push(SetRemoteMerged(guid));
1818 if self.local_tree.is_deleted(guid) {
1819 ops.delete_local_tombstones.push(DeleteLocalTombstone(guid));
1823 // Emit completion ops for deleted items.
1824 for guid in self.deletions() {
1825 signal.err_if_aborted()?;
1827 self.local_tree.node_for_guid(guid),
1828 self.remote_tree.node_for_guid(guid),
1830 (Some(local_node), Some(remote_node)) => {
1831 // Delete items that are non-syncable or invalid on both
1833 ops.delete_local_items.push(DeleteLocalItem(local_node));
1834 ops.insert_local_tombstones
1835 .push(InsertLocalTombstone(remote_node));
1836 ops.upload_tombstones.push(UploadTombstone(guid));
1838 (Some(local_node), None) => {
1839 // Apply remote tombstones, or delete invalid local-only
1840 // items. If the item is deleted remotely, flag the remote
1841 // tombstone as merged. If not, we don't need to upload one,
1842 // since the item is only known locally.
1843 ops.delete_local_items.push(DeleteLocalItem(local_node));
1844 if self.remote_tree.is_deleted(guid) {
1845 ops.set_remote_merged.push(SetRemoteMerged(guid));
1848 (None, Some(remote_node)) => {
1849 // Take local tombstones, or delete invalid remote-only
1850 // items. If it's not already deleted locally, insert a
1851 // tombstone for the item.
1852 if !self.local_tree.is_deleted(guid) {
1853 ops.insert_local_tombstones
1854 .push(InsertLocalTombstone(remote_node));
1856 ops.upload_tombstones.push(UploadTombstone(guid));
1859 // Clean up local tombstones, and flag remote tombstones as
1860 // merged, for items deleted on both sides.
1861 if self.local_tree.is_deleted(guid) {
1862 ops.delete_local_tombstones.push(DeleteLocalTombstone(guid));
1864 if self.remote_tree.is_deleted(guid) {
1865 ops.set_remote_merged.push(SetRemoteMerged(guid));
1874 /// Returns a sequence of completion ops, without interruption.
1876 pub fn completion_ops(&self) -> CompletionOps<'_> {
1877 self.completion_ops_with_signal(&DefaultAbortSignal)
1881 /// Returns an iterator for all accepted local and remote deletions.
1883 pub fn deletions(&self) -> impl Iterator<Item = &Guid> {
1884 self.delete_locally.union(&self.delete_remotely)
1887 /// Returns an iterator for all items that should be deleted from the
1890 pub fn local_deletions(&self) -> impl Iterator<Item = &Guid> {
1891 self.delete_locally.difference(&self.delete_remotely)
1894 /// Returns an iterator for all items that should be deleted from the
1897 pub fn remote_deletions(&self) -> impl Iterator<Item = &Guid> {
1898 self.delete_remotely.iter()
1901 /// Returns structure change counts for this merged root.
1903 pub fn counts(&self) -> &StructureCounts {
1904 &self.structure_counts
1908 /// Completion operations to apply to the local tree after a merge. These are
1909 /// represented as separate structs in `Vec`s instead of enums yielded from an
1910 /// iterator so that consumers can easily chunk them.
1911 #[derive(Clone, Debug, Default)]
1912 pub struct CompletionOps<'t> {
1913 pub change_guids: Vec<ChangeGuid<'t>>,
1914 pub apply_remote_items: Vec<ApplyRemoteItem<'t>>,
1915 pub apply_new_local_structure: Vec<ApplyNewLocalStructure<'t>>,
1916 pub set_local_unmerged: Vec<SetLocalUnmerged<'t>>,
1917 pub set_local_merged: Vec<SetLocalMerged<'t>>,
1918 pub set_remote_merged: Vec<SetRemoteMerged<'t>>,
1919 pub delete_local_tombstones: Vec<DeleteLocalTombstone<'t>>,
1920 pub insert_local_tombstones: Vec<InsertLocalTombstone<'t>>,
1921 pub delete_local_items: Vec<DeleteLocalItem<'t>>,
1922 pub upload_items: Vec<UploadItem<'t>>,
1923 pub upload_tombstones: Vec<UploadTombstone<'t>>,
1926 impl<'t> CompletionOps<'t> {
1927 /// Returns `true` if there are no completion ops to apply.
1929 pub fn is_empty(&self) -> bool {
1930 self.change_guids.is_empty()
1931 && self.apply_remote_items.is_empty()
1932 && self.apply_new_local_structure.is_empty()
1933 && self.set_local_unmerged.is_empty()
1934 && self.set_local_merged.is_empty()
1935 && self.set_remote_merged.is_empty()
1936 && self.delete_local_tombstones.is_empty()
1937 && self.insert_local_tombstones.is_empty()
1938 && self.delete_local_items.is_empty()
1939 && self.upload_items.is_empty()
1940 && self.upload_tombstones.is_empty()
1943 /// Returns a printable summary of all completion ops to apply.
1944 pub fn summarize(&self) -> Vec<String> {
1946 .chain(to_strings(&self.change_guids))
1947 .chain(to_strings(&self.apply_remote_items))
1948 .chain(to_strings(&self.apply_new_local_structure))
1949 .chain(to_strings(&self.set_local_unmerged))
1950 .chain(to_strings(&self.set_local_merged))
1951 .chain(to_strings(&self.set_remote_merged))
1952 .chain(to_strings(&self.delete_local_tombstones))
1953 .chain(to_strings(&self.insert_local_tombstones))
1954 .chain(to_strings(&self.delete_local_items))
1955 .chain(to_strings(&self.upload_items))
1956 .chain(to_strings(&self.upload_tombstones))
1961 /// A completion op to change the local GUID to the merged GUID. This is used
1962 /// to dedupe new local items to remote ones, as well as to fix up invalid
1964 #[derive(Clone, Copy, Debug)]
1965 pub struct ChangeGuid<'t> {
1966 /// The merged node to update.
1967 pub merged_node: &'t MergedNode<'t>,
1968 /// The level of the node in the merged tree. Desktop uses this to ensure
1969 /// that GUID change observers are notified in level order (parents before
1974 impl<'t> ChangeGuid<'t> {
1975 /// Returns the local node for this completion op. Panics if the local node
1976 /// isn't set, as we should never emit a `ChangeGuid` op in that case.
1978 pub fn local_node(&self) -> &'t Node<'t> {
1982 .expect("Can't change local GUID without local node")
1986 impl<'t> fmt::Display for ChangeGuid<'t> {
1987 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1991 self.local_node().guid,
1992 self.merged_node.guid
1997 /// A completion op to insert a new remote item into the local tree, or apply
1998 /// synced changes to an existing item.
1999 #[derive(Clone, Copy, Debug)]
2000 pub struct ApplyRemoteItem<'t> {
2001 pub merged_node: &'t MergedNode<'t>,
2005 impl<'t> ApplyRemoteItem<'t> {
2006 /// Returns the remote node for this completion op. Panics if the remote
2007 /// node isn't set, as we should never emit an `ApplyRemoteItem` op in
2010 pub fn remote_node(&self) -> &'t Node<'t> {
2014 .expect("Can't apply remote item without remote node")
2018 impl<'t> fmt::Display for ApplyRemoteItem<'t> {
2019 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020 if self.merged_node.remote_guid_changed() {
2023 "Apply remote {} as {}",
2024 self.remote_node().guid,
2025 self.merged_node.guid
2028 write!(f, "Apply remote {}", self.merged_node.guid)
2033 /// A completion op to update the parent and position of a local item.
2034 #[derive(Clone, Copy, Debug)]
2035 pub struct ApplyNewLocalStructure<'t> {
2036 pub merged_node: &'t MergedNode<'t>,
2037 pub merged_parent_node: &'t MergedNode<'t>,
2038 pub position: usize,
2042 impl<'t> fmt::Display for ApplyNewLocalStructure<'t> {
2043 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2046 "Move {} into {} at {}",
2047 self.merged_node.guid, self.merged_parent_node.guid, self.position
2052 /// A completion op to flag a local item for upload.
2053 #[derive(Clone, Copy, Debug)]
2054 pub struct SetLocalUnmerged<'t> {
2055 pub merged_node: &'t MergedNode<'t>,
2058 impl<'t> fmt::Display for SetLocalUnmerged<'t> {
2059 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2060 write!(f, "Flag local {} as unmerged", self.merged_node.guid)
2064 /// A completion op to skip uploading a local item after resolving merge
2066 #[derive(Clone, Copy, Debug)]
2067 pub struct SetLocalMerged<'t> {
2068 pub merged_node: &'t MergedNode<'t>,
2071 impl<'t> fmt::Display for SetLocalMerged<'t> {
2072 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2073 write!(f, "Flag local {} as merged", self.merged_node.guid)
2077 /// A completion op to upload or reupload a merged item.
2078 #[derive(Clone, Copy, Debug)]
2079 pub struct UploadItem<'t> {
2080 pub merged_node: &'t MergedNode<'t>,
2083 impl<'t> fmt::Display for UploadItem<'t> {
2084 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2085 write!(f, "Upload item {}", self.merged_node.guid)
2089 /// A completion op to upload a tombstone.
2090 #[derive(Clone, Copy, Debug)]
2091 pub struct UploadTombstone<'t>(&'t Guid);
2093 impl<'t> UploadTombstone<'t> {
2094 /// Returns the GUID to use for the tombstone.
2096 pub fn guid(self) -> &'t Guid {
2101 impl<'t> fmt::Display for UploadTombstone<'t> {
2102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2103 write!(f, "Upload tombstone {}", self.0)
2107 /// A completion op to flag a remote item as merged.
2108 #[derive(Clone, Copy, Debug)]
2109 pub struct SetRemoteMerged<'t>(&'t Guid);
2111 impl<'t> SetRemoteMerged<'t> {
2112 /// Returns the remote GUID for the item to flag as merged.
2114 pub fn guid(self) -> &'t Guid {
2119 impl<'t> fmt::Display for SetRemoteMerged<'t> {
2120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2121 write!(f, "Flag remote {} as merged", self.guid())
2125 /// A completion op to store a tombstone for a remote item.
2126 #[derive(Clone, Copy, Debug)]
2127 pub struct InsertLocalTombstone<'t>(Node<'t>);
2129 impl<'t> InsertLocalTombstone<'t> {
2130 /// Returns the node for the item to delete remotely.
2132 pub fn remote_node(&self) -> Node<'t> {
2137 impl<'t> fmt::Display for InsertLocalTombstone<'t> {
2138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2139 write!(f, "Insert local tombstone {}", self.0.guid)
2143 /// A completion op to delete a local tombstone.
2144 #[derive(Clone, Copy, Debug)]
2145 pub struct DeleteLocalTombstone<'t>(&'t Guid);
2147 impl<'t> DeleteLocalTombstone<'t> {
2148 /// Returns the GUID of the tombstone.
2150 pub fn guid(self) -> &'t Guid {
2155 impl<'t> fmt::Display for DeleteLocalTombstone<'t> {
2156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2157 write!(f, "Delete local tombstone {}", self.0)
2161 /// A completion op to delete an item from the local tree.
2162 #[derive(Clone, Copy, Debug)]
2163 pub struct DeleteLocalItem<'t>(Node<'t>);
2165 impl<'t> DeleteLocalItem<'t> {
2166 // Returns the node for the item to delete locally.
2168 pub fn local_node(&self) -> Node<'t> {
2173 impl<'t> fmt::Display for DeleteLocalItem<'t> {
2174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2175 write!(f, "Delete local item {}", self.0.guid)
2179 /// Recursively accumulates completion ops, starting at `merged_node` and
2180 /// drilling down into all its descendants.
2181 fn accumulate<'t, A: AbortSignal>(
2183 ops: &mut CompletionOps<'t>,
2184 merged_node: &'t MergedNode<'t>,
2188 for (position, merged_child_node) in merged_node.merged_children.iter().enumerate() {
2189 signal.err_if_aborted()?;
2190 let is_tagging = if merged_child_node.guid == TAGS_GUID {
2195 if merged_child_node.merge_state.should_apply_item() {
2196 let apply_remote_item = ApplyRemoteItem {
2197 merged_node: merged_child_node,
2200 ops.apply_remote_items.push(apply_remote_item);
2202 if merged_child_node.local_guid_changed() {
2203 let change_guid = ChangeGuid {
2204 merged_node: merged_child_node,
2207 ops.change_guids.push(change_guid);
2209 let local_child_node = merged_node
2212 .and_then(|local_parent_node| local_parent_node.child(position));
2213 let merged_local_child_node = merged_child_node.merge_state.local_node();
2215 .and_then(|m| merged_local_child_node.map(|n| m.guid != n.guid))
2218 // As an optimization, we only emit ops to apply a new local
2219 // structure for items that actually moved. For example, if the
2220 // local children are (A B C D) and the merged children are
2221 // (A D C B), only (B D) need new structure.
2222 let apply_new_local_structure = ApplyNewLocalStructure {
2223 merged_node: merged_child_node,
2224 merged_parent_node: merged_node,
2228 ops.apply_new_local_structure
2229 .push(apply_new_local_structure);
2231 let local_needs_merge = merged_child_node
2234 .map(|node| node.needs_merge)
2236 let should_upload = merged_child_node.merge_state.should_upload();
2237 match (local_needs_merge, should_upload) {
2239 // Local item isn't flagged for upload, but should be.
2240 let set_local_unmerged = SetLocalUnmerged {
2241 merged_node: merged_child_node,
2243 ops.set_local_unmerged.push(set_local_unmerged);
2246 // Local item flagged for upload when it doesn't need to be.
2247 let set_local_merged = SetLocalMerged {
2248 merged_node: merged_child_node,
2250 ops.set_local_merged.push(set_local_merged);
2254 if should_upload && !is_tagging {
2255 // (Re)upload items. Ignore the tags root and its descendants:
2256 // they're part of the local tree on Desktop (and will be removed
2257 // in bug 424160), but aren't synced as part of the structure.
2258 ops.upload_items.push(UploadItem {
2259 merged_node: merged_child_node,
2262 if let Some(remote_child_node) = merged_child_node.merge_state.remote_node() {
2263 if remote_child_node.needs_merge && !should_upload {
2264 // If the remote item was merged, and doesn't need to be
2265 // reuploaded, flag it as merged in the remote tree. Note that
2266 // we _don't_ emit this for locally revived items, or items with
2267 // new remote structure.
2268 let set_remote_merged = SetRemoteMerged(&remote_child_node.guid);
2269 ops.set_remote_merged.push(set_remote_merged);
2272 accumulate(signal, ops, merged_child_node, level + 1, is_tagging)?;
2277 /// Converts all items in the list to strings.
2278 pub(crate) fn to_strings<'a, T: ToString>(items: &'a [T]) -> impl Iterator<Item = String> + 'a {
2279 items.iter().map(ToString::to_string)