Bug 1885602 - Part 2: Add a MozillaAccountMenuButton composable for the menu redesign...
[gecko.git] / third_party / rust / dogear / src / merge.rs
blob920e55c29504f324ba21edb12203a0fd5c5b4454
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
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
15 use std::{
16     collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
17     fmt, mem,
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.
30     Unchanged,
31     /// Node moved on the other side.
32     Moved,
33     /// Node deleted on the other side.
34     Deleted,
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.
49     pub dupes: usize,
50     /// Total number of nodes in the merged tree, excluding the
51     /// root.
52     pub merged_nodes: usize,
55 /// Holds (matching remote dupes for local GUIDs, matching local dupes for
56 /// remote GUIDs).
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 {
62     Local,
63     Remote,
64     Unchanged,
67 /// A hash key used to match dupes by content.
68 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
69 enum DupeKey<'a> {
70     /// Matches a dupe by content only. Used for bookmarks, queries, folders,
71     /// and livemarks.
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.
79 ///
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.
85 ///
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.
89 ///
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.
94 ///
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> {
100     driver: &'t D,
101     signal: &'t A,
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> {
114         Merger {
115             driver: &DefaultDriver,
116             signal: &DefaultAbortSignal,
117             local_tree,
118             remote_tree,
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(),
124         }
125     }
128 impl<'t, D: Driver, A: AbortSignal> Merger<'t, D, A> {
129     /// Creates a merger with the given merge driver and contents.
130     pub fn with_driver(
131         driver: &'t D,
132         signal: &'t A,
133         local_tree: &'t Tree,
134         remote_tree: &'t Tree,
135     ) -> Merger<'t, D, A> {
136         Merger {
137             driver,
138             signal,
139             local_tree,
140             remote_tree,
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(),
146         }
147     }
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)?
155         };
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());
165             }
166         }
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());
171             }
172         }
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());
181             }
182         }
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());
187             }
188         }
190         Ok(MergedRoot {
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,
198         })
199     }
201     #[inline]
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)
206     }
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()
215         } else {
216             warn!(
217                 self.driver,
218                 "Generating new GUID for local node {}", local_node
219             );
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());
225                 }
226                 self.merged_guids.insert(new_guid.clone());
227             }
228             new_guid
229         };
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
235         // or deleted.
236         for local_child_node in local_node.children() {
237             self.signal.err_if_aborted()?;
238             self.merge_local_child_into_merged_node(
239                 &mut merged_node,
240                 local_node,
241                 None,
242                 local_child_node,
243             )?;
244         }
246         if local_node.diverged() {
247             merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
248         }
250         Ok(merged_node)
251     }
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()
260         } else {
261             warn!(
262                 self.driver,
263                 "Generating new GUID for remote node {}", remote_node
264             );
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());
270                 }
271                 self.merged_guids.insert(new_guid.clone());
272                 // Upload tombstones for changed remote GUIDs.
273                 self.delete_remotely.insert(remote_node.guid.clone());
274             }
275             new_guid
276         };
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(
284                 &mut merged_node,
285                 None,
286                 remote_node,
287                 remote_child_node,
288             )?;
289         }
291         if remote_node.diverged()
292             || merged_node.remote_guid_changed()
293             || remote_node.validity != Validity::Valid
294         {
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();
298         }
300         Ok(merged_node)
301     }
303     /// Merges two nodes that exist locally and remotely.
304     fn two_way_merge(
305         &mut self,
306         local_node: Node<'t>,
307         remote_node: Node<'t>,
308     ) -> Result<MergedNode<'t>> {
309         trace!(
310             self.driver,
311             "Item exists locally as {} and remotely as {}",
312             local_node,
313             remote_node
314         );
316         if !local_node.has_compatible_kind(&remote_node) {
317             error!(
318                 self.driver,
319                 "Merging local {} and remote {} with different kinds", local_node, remote_node
320             );
321             return Err(ErrorKind::MismatchedItemKind(
322                 local_node.item().clone(),
323                 remote_node.item().clone(),
324             )
325             .into());
326         }
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()
333         } else {
334             warn!(
335                 self.driver,
336                 "Generating new valid GUID for node {}", remote_node
337             );
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());
343                 }
344                 self.merged_guids.insert(new_guid.clone());
345                 // Upload tombstones for changed remote GUIDs.
346                 self.delete_remotely.insert(remote_node.guid.clone());
347             }
348             new_guid
349         };
351         let (item, children) = self.resolve_value_conflict(local_node, remote_node);
353         let mut merged_node = MergedNode::new(
354             merged_guid,
355             match item {
356                 ConflictResolution::Local => MergeState::Local {
357                     local_node,
358                     remote_node,
359                 },
360                 ConflictResolution::Remote => MergeState::Remote {
361                     local_node,
362                     remote_node,
363                 },
364                 ConflictResolution::Unchanged => MergeState::Unchanged {
365                     local_node,
366                     remote_node,
367                 },
368             },
369         );
371         match children {
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(
376                         &mut merged_node,
377                         local_node,
378                         Some(remote_node),
379                         local_child_node,
380                     )?;
381                 }
382                 for remote_child_node in remote_node.children() {
383                     self.signal.err_if_aborted()?;
384                     self.merge_remote_child_into_merged_node(
385                         &mut merged_node,
386                         Some(local_node),
387                         remote_node,
388                         remote_child_node,
389                     )?;
390                 }
391             }
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(
397                         &mut merged_node,
398                         Some(local_node),
399                         remote_node,
400                         remote_child_node,
401                     )?;
402                 }
403                 for local_child_node in local_node.children() {
404                     self.signal.err_if_aborted()?;
405                     self.merge_local_child_into_merged_node(
406                         &mut merged_node,
407                         local_node,
408                         Some(remote_node),
409                         local_child_node,
410                     )?;
411                 }
412             }
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())
418                 {
419                     self.signal.err_if_aborted()?;
420                     self.merge_unchanged_child_into_merged_node(
421                         &mut merged_node,
422                         local_node,
423                         local_child_node,
424                         remote_node,
425                         remote_child_node,
426                     )?;
427                 }
428             }
429         }
431         if local_node.diverged() {
432             merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
433         }
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();
438         }
440         Ok(merged_node)
441     }
443     /// Merges two nodes with the same parents and positions.
444     ///
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(
448         &mut self,
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>,
454     ) -> Result<()> {
455         assert!(
456             !self.merged_guids.contains(&local_child_node.guid),
457             "Unchanged local child shouldn't have been merged"
458         );
459         assert!(
460             !self.merged_guids.contains(&remote_child_node.guid),
461             "Unchanged remote child shouldn't have been merged"
462         );
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
466         // changes.
467         let local_structure_change = self.check_for_local_structure_change_of_remote_node(
468             merged_node,
469             remote_parent_node,
470             remote_child_node,
471         )?;
472         let remote_structure_change = self.check_for_remote_structure_change_of_local_node(
473             merged_node,
474             local_parent_node,
475             local_child_node,
476         )?;
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
482                     .merge_state
483                     .with_new_local_structure()
484                     .with_new_remote_structure();
485             }
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();
490             }
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();
495             }
496             (_, _) => {
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();
505                 }
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();
511                 }
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();
516                 }
517                 merged_node.merged_children.push(merged_child_node);
518                 self.structure_counts.merged_nodes += 1;
519             }
520         }
522         Ok(())
523     }
525     /// Merges a remote child node into a merged folder node. This handles the
526     /// following cases:
527     ///
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
532     ///   child.
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.
537     ///
538     /// This is the inverse of `merge_local_child_into_merged_node`.
539     fn merge_remote_child_into_merged_node(
540         &mut self,
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>,
545     ) -> Result<()> {
546         if self.merged_guids.contains(&remote_child_node.guid) {
547             trace!(
548                 self.driver,
549                 "Remote child {} already seen in another folder and merged",
550                 remote_child_node
551             );
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();
555             return Ok(());
556         }
558         trace!(
559             self.driver,
560             "Merging remote child {} of {} into {}",
561             remote_child_node,
562             remote_parent_node,
563             merged_node
564         );
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(
573             merged_node,
574             remote_parent_node,
575             remote_child_node,
576         )? == StructureChange::Deleted
577         {
578             // Flag the merged parent for reupload, since we deleted the
579             // remote child.
580             merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
581             return Ok(());
582         }
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
588                 .parent()
589                 .expect("Can't merge existing remote child without local parent");
591             trace!(
592                 self.driver,
593                 "Remote child {} exists locally in {} and remotely in {}",
594                 remote_child_node,
595                 local_parent_node,
596                 remote_parent_node
597             );
599             if self.remote_tree.is_deleted(&local_parent_node.guid) {
600                 trace!(
601                     self.driver,
602                     "Unconditionally taking remote move for {} to {} because local parent {} is \
603                      deleted remotely",
604                     remote_child_node,
605                     remote_parent_node,
606                     local_parent_node
607                 );
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();
618                 }
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();
623                 }
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;
627                 return Ok(());
628             }
630             match self.resolve_structure_conflict(
631                 local_parent_node,
632                 local_child_node,
633                 remote_parent_node,
634                 remote_child_node,
635             ) {
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
639                     // local parent.
640                     trace!(
641                         self.driver,
642                         "Remote child {} moved locally to {} and remotely to {}; \
643                          keeping child in newer local parent and position",
644                         remote_child_node,
645                         local_parent_node,
646                         remote_parent_node
647                     );
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();
654                 }
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
660                     {
661                         trace!(
662                             self.driver,
663                             "Remote child {} reparented locally to {} and remotely to {}; \
664                              keeping child in newer remote parent",
665                             remote_child_node,
666                             local_parent_node,
667                             remote_parent_node
668                         );
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();
673                         merged_child_node
674                     } else {
675                         trace!(
676                             self.driver,
677                             "Remote child {} repositioned locally in {} and remotely in {}; \
678                              keeping child in newer remote position",
679                             remote_child_node,
680                             local_parent_node,
681                             remote_parent_node
682                         );
683                         self.two_way_merge(local_child_node, remote_child_node)?
684                     };
685                     if merged_child_node.local_guid_changed() {
686                         merged_child_node.merge_state =
687                             merged_child_node.merge_state.with_new_local_structure();
688                     }
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();
694                     }
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();
700                     }
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;
704                 }
705             }
707             return Ok(());
708         }
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
712         // we can.
713         trace!(
714             self.driver,
715             "Remote child {} doesn't exist locally; looking for local content match",
716             remote_child_node
717         );
719         let mut merged_child_node = if let Some(local_child_node_by_content) = self
720             .find_local_node_matching_remote_node(
721                 merged_node,
722                 local_parent_node,
723                 remote_parent_node,
724                 remote_child_node,
725             )? {
726             self.two_way_merge(local_child_node_by_content, remote_child_node)
727         } else {
728             self.merge_remote_only_node(remote_child_node)
729         }?;
730         if merged_child_node.local_guid_changed() {
731             merged_child_node.merge_state =
732                 merged_child_node.merge_state.with_new_local_structure();
733         }
734         if merged_node.remote_guid_changed() {
735             merged_child_node.merge_state =
736                 merged_child_node.merge_state.with_new_remote_structure();
737         }
738         if merged_child_node.remote_guid_changed() {
739             merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
740         }
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;
744         Ok(())
745     }
747     /// Merges a local child node into a merged folder node.
748     ///
749     /// This is the inverse of `merge_remote_child_into_merged_node`.
750     fn merge_local_child_into_merged_node(
751         &mut self,
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>,
756     ) -> Result<()> {
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.
761             trace!(
762                 self.driver,
763                 "Local child {} already seen in another folder and merged",
764                 local_child_node
765             );
766             merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
767             return Ok(());
768         }
770         trace!(
771             self.driver,
772             "Merging local child {} of {} into {}",
773             local_child_node,
774             local_parent_node,
775             merged_node
776         );
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(
781             merged_node,
782             local_parent_node,
783             local_child_node,
784         )? == StructureChange::Deleted
785         {
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();
789             return Ok(());
790         }
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
798                 .parent()
799                 .expect("Can't merge existing local child without remote parent");
801             trace!(
802                 self.driver,
803                 "Local child {} exists locally in {} and remotely in {}",
804                 local_child_node,
805                 local_parent_node,
806                 remote_parent_node
807             );
809             if self.local_tree.is_deleted(&remote_parent_node.guid) {
810                 trace!(
811                     self.driver,
812                     "Unconditionally taking local move for {} to {} because remote parent {} is \
813                      deleted locally",
814                     local_child_node,
815                     local_parent_node,
816                     remote_parent_node
817                 );
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`.
822                 //
823                 // Reuploading the child isn't necessary for newer Desktops, which
824                 // ignore the child's `parentid` and use the parent's `children`.
825                 //
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();
833                 }
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;
839                 return Ok(());
840             }
842             match self.resolve_structure_conflict(
843                 local_parent_node,
844                 local_child_node,
845                 remote_parent_node,
846                 remote_child_node,
847             ) {
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.
853                         trace!(
854                             self.driver,
855                             "Local child {} reparented locally to {} and remotely to {}; \
856                              keeping child in newer local parent",
857                             local_child_node,
858                             local_parent_node,
859                             remote_parent_node
860                         );
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();
869                         }
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;
876                     } else {
877                         trace!(
878                             self.driver,
879                             "Local child {} repositioned locally in {} and remotely in {}; \
880                              keeping child in newer local position",
881                             local_child_node,
882                             local_parent_node,
883                             remote_parent_node
884                         );
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();
893                         }
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();
903                         }
905                         merged_node.merged_children.push(merged_child_node);
906                         self.structure_counts.merged_nodes += 1;
907                     }
908                 }
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 {
915                         trace!(
916                             self.driver,
917                             "Local child {} reparented locally to {} and remotely to {}; \
918                              keeping child in newer remote parent",
919                             local_child_node,
920                             local_parent_node,
921                             remote_parent_node
922                         );
923                     } else {
924                         trace!(
925                             self.driver,
926                             "Local child {} repositioned locally in {} and remotely in {}; \
927                              keeping child in newer remote position",
928                             local_child_node,
929                             local_parent_node,
930                             remote_parent_node
931                         );
932                     }
933                     merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
934                 }
935             }
937             return Ok(());
938         }
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
942         // we can.
943         trace!(
944             self.driver,
945             "Local child {} doesn't exist remotely; looking for remote content match",
946             local_child_node
947         );
949         let merged_child_node = if let Some(remote_child_node_by_content) = self
950             .find_remote_node_matching_local_node(
951                 merged_node,
952                 local_parent_node,
953                 remote_parent_node,
954                 local_child_node,
955             )? {
956             // The local child has a remote content match, so take the remote GUID
957             // and merge.
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();
963             }
964             if merged_node.remote_guid_changed() {
965                 merged_child_node.merge_state =
966                     merged_child_node.merge_state.with_new_remote_structure();
967             }
968             if merged_child_node.remote_guid_changed() {
969                 merged_node.merge_state = merged_node.merge_state.with_new_remote_structure();
970             }
971             merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
972             merged_child_node
973         } else {
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();
980             }
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();
984             merged_child_node
985         };
986         merged_node.merged_children.push(merged_child_node);
987         self.structure_counts.merged_nodes += 1;
988         Ok(())
989     }
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(
994         &self,
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);
1001         }
1003         match (local_node.needs_merge, remote_node.needs_merge) {
1004             (true, true) => {
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
1010                 } else {
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,
1022                         (_, _) => {
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
1028                             } else {
1029                                 ConflictResolution::Remote
1030                             }
1031                         }
1032                     }
1033                 };
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
1042                 } else {
1043                     // The remote change is newer, so walk and merge remote
1044                     // children first, then remaining local children.
1045                     ConflictResolution::Remote
1046                 };
1047                 (item, children)
1048             }
1050             (true, false) => {
1051                 // The item changed locally, but not remotely. Prefer the local
1052                 // item, then merge local children first, followed by remote
1053                 // children.
1054                 let item = match local_node.validity {
1055                     Validity::Valid | Validity::Reupload => ConflictResolution::Local,
1056                     Validity::Replace => ConflictResolution::Remote,
1057                 };
1058                 let children = if local_node.has_matching_children(remote_node) {
1059                     ConflictResolution::Unchanged
1060                 } else {
1061                     ConflictResolution::Local
1062                 };
1063                 (item, children)
1064             }
1066             (false, true) => {
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
1071                 } else {
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,
1078                     }
1079                 };
1080                 let children = if local_node.has_matching_children(remote_node) {
1081                     ConflictResolution::Unchanged
1082                 } else {
1083                     ConflictResolution::Remote
1084                 };
1085                 // For children, we always use the remote side.
1086                 (item, children)
1087             }
1089             (false, false) => {
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,
1095                 };
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
1103                 } else {
1104                     ConflictResolution::Remote
1105                 };
1106                 (item, children)
1107             }
1108         }
1109     }
1111     /// Determines where to keep a child of a folder that exists on both sides.
1112     fn resolve_structure_conflict(
1113         &self,
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;
1122         }
1124         match (
1125             local_parent_node.needs_merge,
1126             remote_parent_node.needs_merge,
1127         ) {
1128             (true, true) => {
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
1136                 } else {
1137                     ConflictResolution::Remote
1138                 }
1139             }
1141             // If only the local or remote parent changed, keep the child in its
1142             // new parent.
1143             (true, false) => ConflictResolution::Local,
1144             (false, true) => ConflictResolution::Remote,
1146             (false, false) => ConflictResolution::Unchanged,
1147         }
1148     }
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.
1152     ///
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(
1156         &mut self,
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.
1164             trace!(
1165                 self.driver,
1166                 "Deleting non-syncable remote node {}",
1167                 remote_node
1168             );
1169             return self.delete_remote_node(merged_node, remote_node);
1170         }
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.
1177                     trace!(
1178                         self.driver,
1179                         "Remote node {} is syncable, but local node {} isn't; deleting",
1180                         remote_node,
1181                         local_node
1182                     );
1183                     return self.delete_remote_node(merged_node, remote_node);
1184                 }
1185                 if local_node.validity == Validity::Replace
1186                     && remote_node.validity == Validity::Replace
1187                 {
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);
1191                 }
1192                 let local_parent_node = local_node
1193                     .parent()
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);
1197                 }
1198                 return Ok(StructureChange::Unchanged);
1199             }
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);
1204             }
1205             return Ok(StructureChange::Unchanged);
1206         }
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);
1212         }
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);
1217         }
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.
1223                 trace!(
1224                     self.driver,
1225                     "Remote non-folder {} deleted locally and changed remotely; \
1226                      taking remote change",
1227                     remote_node
1228                 );
1229                 self.structure_counts.remote_revives += 1;
1230                 return Ok(StructureChange::Unchanged);
1231             }
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.
1236             trace!(
1237                 self.driver,
1238                 "Remote folder {} deleted locally and changed remotely; \
1239                  taking local deletion",
1240                 remote_node
1241             );
1242             self.structure_counts.local_deletes += 1;
1243         } else {
1244             trace!(
1245                 self.driver,
1246                 "Remote node {} deleted locally and not changed remotely; \
1247                  taking local deletion",
1248                 remote_node
1249             );
1250         }
1252         // Take the local deletion and relocate any new remote descendants to the
1253         // merged node.
1254         self.delete_remote_node(merged_node, remote_node)
1255     }
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.
1259     ///
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(
1263         &mut self,
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.
1271             trace!(
1272                 self.driver,
1273                 "Deleting non-syncable local node {}",
1274                 local_node
1275             );
1276             return self.delete_local_node(merged_node, local_node);
1277         }
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.
1287                     trace!(
1288                         self.driver,
1289                         "Local node {} is syncable, but remote node {} isn't; deleting",
1290                         local_node,
1291                         remote_node
1292                     );
1293                     return self.delete_local_node(merged_node, local_node);
1294                 }
1295                 if remote_node.validity == Validity::Replace
1296                     && local_node.validity == Validity::Replace
1297                 {
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);
1301                 }
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
1304                 // valid copy.
1305                 let remote_parent_node = remote_node
1306                     .parent()
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);
1310                 }
1311                 return Ok(StructureChange::Unchanged);
1312             }
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);
1317             }
1318             return Ok(StructureChange::Unchanged);
1319         }
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);
1325         }
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);
1330         }
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() {
1336                 trace!(
1337                     self.driver,
1338                     "Local non-folder {} deleted remotely and changed locally; taking local change",
1339                     local_node
1340                 );
1341                 self.structure_counts.local_revives += 1;
1342                 return Ok(StructureChange::Unchanged);
1343             }
1344             trace!(
1345                 self.driver,
1346                 "Local folder {} deleted remotely and changed locally; taking remote deletion",
1347                 local_node
1348             );
1349             self.structure_counts.remote_deletes += 1;
1350         } else {
1351             trace!(
1352                 self.driver,
1353                 "Local node {} deleted remotely and not changed locally; taking remote deletion",
1354                 local_node
1355             );
1356         }
1358         // Take the remote deletion and relocate any new local descendants to the
1359         // merged node.
1360         self.delete_local_node(merged_node, local_node)
1361     }
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.
1367     ///
1368     /// This is the inverse of `delete_local_node`.
1369     fn delete_remote_node(
1370         &mut self,
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) {
1378                 trace!(
1379                     self.driver,
1380                     "Remote child {} can't be an orphan; already merged",
1381                     remote_child_node
1382                 );
1383                 continue;
1384             }
1385             match self.check_for_local_structure_change_of_remote_node(
1386                 merged_node,
1387                 remote_node,
1388                 remote_child_node,
1389             )? {
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.
1393                     continue;
1394                 }
1395                 StructureChange::Unchanged => {
1396                     trace!(
1397                         self.driver,
1398                         "Relocating remote orphan {} to {}",
1399                         remote_child_node,
1400                         merged_node
1401                     );
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)
1406                     {
1407                         self.two_way_merge(local_child_node, remote_child_node)
1408                     } else {
1409                         self.merge_remote_only_node(remote_child_node)
1410                     }?;
1411                     merged_node.merge_state = merged_node
1412                         .merge_state
1413                         .with_new_local_structure()
1414                         .with_new_remote_structure();
1415                     merged_orphan_node.merge_state = merged_orphan_node
1416                         .merge_state
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;
1421                 }
1422             }
1423         }
1424         Ok(StructureChange::Deleted)
1425     }
1427     /// Marks a local node as deleted, and relocates all local descendants
1428     /// that aren't also remotely deleted to the merged node.
1429     ///
1430     /// This is the inverse of `delete_remote_node`.
1431     fn delete_local_node(
1432         &mut self,
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) {
1440                 trace!(
1441                     self.driver,
1442                     "Local child {} can't be an orphan; already merged",
1443                     local_child_node
1444                 );
1445                 continue;
1446             }
1447             match self.check_for_remote_structure_change_of_local_node(
1448                 merged_node,
1449                 local_node,
1450                 local_child_node,
1451             )? {
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.
1455                     continue;
1456                 }
1457                 StructureChange::Unchanged => {
1458                     trace!(
1459                         self.driver,
1460                         "Relocating local orphan {} to {}",
1461                         local_child_node,
1462                         merged_node
1463                     );
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)
1468                     {
1469                         self.two_way_merge(local_child_node, remote_child_node)
1470                     } else {
1471                         self.merge_local_only_node(local_child_node)
1472                     }?;
1473                     merged_node.merge_state = merged_node
1474                         .merge_state
1475                         .with_new_local_structure()
1476                         .with_new_remote_structure();
1477                     merged_orphan_node.merge_state = merged_orphan_node
1478                         .merge_state
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;
1483                 }
1484             }
1485         }
1486         Ok(StructureChange::Deleted)
1487     }
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.
1492     ///
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.
1496     ///
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.
1502     ///
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(
1510         &self,
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() {
1519                 trace!(
1520                     self.driver,
1521                     "Not deduping local built-in root {}",
1522                     local_child_node
1523                 );
1524                 continue;
1525             }
1526             if self.remote_tree.mentions(&local_child_node.guid) {
1527                 trace!(
1528                     self.driver,
1529                     "Not deduping local child {}; already deleted or exists remotely",
1530                     local_child_node
1531                 );
1532                 continue;
1533             }
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)
1542                         }
1543                         Content::Separator => {
1544                             DupeKey::WithPosition(local_child_content, local_position)
1545                         }
1546                     };
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);
1549                 }
1550                 None => {
1551                     trace!(
1552                         self.driver,
1553                         "Not deduping local child {} without content info",
1554                         local_child_node
1555                     );
1556                 }
1557             }
1558         }
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() {
1566                 trace!(
1567                     self.driver,
1568                     "Not deduping remote built-in root {}",
1569                     remote_child_node
1570                 );
1571                 continue;
1572             }
1573             if self.local_tree.mentions(&remote_child_node.guid) {
1574                 trace!(
1575                     self.driver,
1576                     "Not deduping remote child {}; already deleted or exists locally",
1577                     remote_child_node
1578                 );
1579                 continue;
1580             }
1581             if remote_to_local.contains_key(&remote_child_node.guid) {
1582                 trace!(
1583                     self.driver,
1584                     "Not deduping remote child {}; already deduped",
1585                     remote_child_node
1586                 );
1587                 continue;
1588             }
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
1591             // were.
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)
1597                         }
1598                         Content::Separator => {
1599                             DupeKey::WithPosition(remote_child_content, remote_position)
1600                         }
1601                     };
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() {
1604                             trace!(
1605                                 self.driver,
1606                                 "Deduping local child {} to remote child {}",
1607                                 local_child_node,
1608                                 remote_child_node
1609                             );
1610                             local_to_remote
1611                                 .insert(local_child_node.guid.clone(), remote_child_node);
1612                             remote_to_local
1613                                 .insert(remote_child_node.guid.clone(), local_child_node);
1614                         } else {
1615                             trace!(
1616                                 self.driver,
1617                                 "Not deduping remote child {}; no remaining local content matches",
1618                                 remote_child_node
1619                             );
1620                             continue;
1621                         }
1622                     } else {
1623                         trace!(
1624                             self.driver,
1625                             "Not deduping remote child {}; no local content matches",
1626                             remote_child_node
1627                         );
1628                         continue;
1629                     }
1630                 }
1631                 None => {
1632                     trace!(
1633                         self.driver,
1634                         "Not deduping remote child {} without content info",
1635                         remote_child_node
1636                     );
1637                 }
1638             }
1639         }
1641         Ok((local_to_remote, remote_to_local))
1642     }
1644     /// Finds a remote node with a different GUID that matches the content of a
1645     /// local node.
1646     ///
1647     /// This is the inverse of `find_local_node_matching_remote_node`.
1648     fn find_remote_node_matching_local_node(
1649         &mut self,
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,
1658                 HashMap::new(),
1659             );
1660             let new_remote_node = {
1661                 let (local_to_remote, _) = match matching_dupes_by_local_parent_guid
1662                     .entry(local_parent_node.guid.clone())
1663                 {
1664                     Entry::Occupied(entry) => entry.into_mut(),
1665                     Entry::Vacant(entry) => {
1666                         trace!(
1667                             self.driver,
1668                             "First local child {} doesn't exist remotely; \
1669                              finding all matching dupes in local {} and remote {}",
1670                             local_child_node,
1671                             local_parent_node,
1672                             remote_parent_node
1673                         );
1674                         let matching_dupes = self.find_all_matching_dupes_in_folders(
1675                             local_parent_node,
1676                             remote_parent_node,
1677                         )?;
1678                         entry.insert(matching_dupes)
1679                     }
1680                 };
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;
1684                     *node
1685                 })
1686             };
1687             self.matching_dupes_by_local_parent_guid = matching_dupes_by_local_parent_guid;
1688             Ok(new_remote_node)
1689         } else {
1690             trace!(
1691                 self.driver,
1692                 "Merged node {} doesn't exist remotely; no potential dupes for local child {}",
1693                 merged_node,
1694                 local_child_node
1695             );
1696             Ok(None)
1697         }
1698     }
1700     /// Finds a local node with a different GUID that matches the content of a
1701     /// remote node.
1702     ///
1703     /// This is the inverse of `find_remote_node_matching_local_node`.
1704     fn find_local_node_matching_remote_node(
1705         &mut self,
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,
1714                 HashMap::new(),
1715             );
1716             let new_local_node = {
1717                 let (_, remote_to_local) = match matching_dupes_by_local_parent_guid
1718                     .entry(local_parent_node.guid.clone())
1719                 {
1720                     Entry::Occupied(entry) => entry.into_mut(),
1721                     Entry::Vacant(entry) => {
1722                         trace!(
1723                             self.driver,
1724                             "First remote child {} doesn't exist locally; \
1725                              finding all matching dupes in local {} and remote {}",
1726                             remote_child_node,
1727                             local_parent_node,
1728                             remote_parent_node
1729                         );
1730                         let matching_dupes = self.find_all_matching_dupes_in_folders(
1731                             local_parent_node,
1732                             remote_parent_node,
1733                         )?;
1734                         entry.insert(matching_dupes)
1735                     }
1736                 };
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;
1740                     *node
1741                 })
1742             };
1743             self.matching_dupes_by_local_parent_guid = matching_dupes_by_local_parent_guid;
1744             Ok(new_local_node)
1745         } else {
1746             trace!(
1747                 self.driver,
1748                 "Merged node {} doesn't exist locally; no potential dupes for remote child {}",
1749                 merged_node,
1750                 remote_child_node
1751             );
1752             Ok(None)
1753         }
1754     }
1757 /// The root of a merged tree, from which all merged nodes descend.
1758 #[derive(Debug)]
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.
1771     #[inline]
1772     pub fn node(&self) -> &MergedNode<'_> {
1773         &self.node
1774     }
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(
1780         &self,
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
1787         // the other side.
1788         for guid in self
1789             .local_tree
1790             .deletions()
1791             .difference(&self.delete_remotely)
1792         {
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));
1800             }
1801         }
1802         for guid in self
1803             .remote_tree
1804             .deletions()
1805             .difference(&self.delete_locally)
1806             .filter(|guid| !self.local_tree.exists(guid))
1807         {
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));
1820             }
1821         }
1823         // Emit completion ops for deleted items.
1824         for guid in self.deletions() {
1825             signal.err_if_aborted()?;
1826             match (
1827                 self.local_tree.node_for_guid(guid),
1828                 self.remote_tree.node_for_guid(guid),
1829             ) {
1830                 (Some(local_node), Some(remote_node)) => {
1831                     // Delete items that are non-syncable or invalid on both
1832                     // sides.
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));
1837                 }
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));
1846                     }
1847                 }
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));
1855                     }
1856                     ops.upload_tombstones.push(UploadTombstone(guid));
1857                 }
1858                 (None, None) => {
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));
1863                     }
1864                     if self.remote_tree.is_deleted(guid) {
1865                         ops.set_remote_merged.push(SetRemoteMerged(guid));
1866                     }
1867                 }
1868             }
1869         }
1871         Ok(ops)
1872     }
1874     /// Returns a sequence of completion ops, without interruption.
1875     #[inline]
1876     pub fn completion_ops(&self) -> CompletionOps<'_> {
1877         self.completion_ops_with_signal(&DefaultAbortSignal)
1878             .unwrap()
1879     }
1881     /// Returns an iterator for all accepted local and remote deletions.
1882     #[inline]
1883     pub fn deletions(&self) -> impl Iterator<Item = &Guid> {
1884         self.delete_locally.union(&self.delete_remotely)
1885     }
1887     /// Returns an iterator for all items that should be deleted from the
1888     /// local tree.
1889     #[inline]
1890     pub fn local_deletions(&self) -> impl Iterator<Item = &Guid> {
1891         self.delete_locally.difference(&self.delete_remotely)
1892     }
1894     /// Returns an iterator for all items that should be deleted from the
1895     /// remote tree.
1896     #[inline]
1897     pub fn remote_deletions(&self) -> impl Iterator<Item = &Guid> {
1898         self.delete_remotely.iter()
1899     }
1901     /// Returns structure change counts for this merged root.
1902     #[inline]
1903     pub fn counts(&self) -> &StructureCounts {
1904         &self.structure_counts
1905     }
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.
1928     #[inline]
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()
1941     }
1943     /// Returns a printable summary of all completion ops to apply.
1944     pub fn summarize(&self) -> Vec<String> {
1945         std::iter::empty()
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))
1957             .collect()
1958     }
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
1963 /// GUIDs.
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
1970     /// children).
1971     pub level: usize,
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.
1977     #[inline]
1978     pub fn local_node(&self) -> &'t Node<'t> {
1979         self.merged_node
1980             .merge_state
1981             .local_node()
1982             .expect("Can't change local GUID without local node")
1983     }
1986 impl<'t> fmt::Display for ChangeGuid<'t> {
1987     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1988         write!(
1989             f,
1990             "Change {} to {}",
1991             self.local_node().guid,
1992             self.merged_node.guid
1993         )
1994     }
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>,
2002     pub level: usize,
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
2008     /// that case.
2009     #[inline]
2010     pub fn remote_node(&self) -> &'t Node<'t> {
2011         self.merged_node
2012             .merge_state
2013             .remote_node()
2014             .expect("Can't apply remote item without remote node")
2015     }
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() {
2021             write!(
2022                 f,
2023                 "Apply remote {} as {}",
2024                 self.remote_node().guid,
2025                 self.merged_node.guid
2026             )
2027         } else {
2028             write!(f, "Apply remote {}", self.merged_node.guid)
2029         }
2030     }
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,
2039     pub level: usize,
2042 impl<'t> fmt::Display for ApplyNewLocalStructure<'t> {
2043     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2044         write!(
2045             f,
2046             "Move {} into {} at {}",
2047             self.merged_node.guid, self.merged_parent_node.guid, self.position
2048         )
2049     }
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)
2061     }
2064 /// A completion op to skip uploading a local item after resolving merge
2065 /// conflicts.
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)
2074     }
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)
2086     }
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.
2095     #[inline]
2096     pub fn guid(self) -> &'t Guid {
2097         self.0
2098     }
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)
2104     }
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.
2113     #[inline]
2114     pub fn guid(self) -> &'t Guid {
2115         self.0
2116     }
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())
2122     }
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.
2131     #[inline]
2132     pub fn remote_node(&self) -> Node<'t> {
2133         self.0
2134     }
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)
2140     }
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.
2149     #[inline]
2150     pub fn guid(self) -> &'t Guid {
2151         self.0
2152     }
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)
2158     }
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.
2167     #[inline]
2168     pub fn local_node(&self) -> Node<'t> {
2169         self.0
2170     }
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)
2176     }
2179 /// Recursively accumulates completion ops, starting at `merged_node` and
2180 /// drilling down into all its descendants.
2181 fn accumulate<'t, A: AbortSignal>(
2182     signal: &A,
2183     ops: &mut CompletionOps<'t>,
2184     merged_node: &'t MergedNode<'t>,
2185     level: usize,
2186     is_tagging: bool,
2187 ) -> Result<()> {
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 {
2191             true
2192         } else {
2193             is_tagging
2194         };
2195         if merged_child_node.merge_state.should_apply_item() {
2196             let apply_remote_item = ApplyRemoteItem {
2197                 merged_node: merged_child_node,
2198                 level,
2199             };
2200             ops.apply_remote_items.push(apply_remote_item);
2201         }
2202         if merged_child_node.local_guid_changed() {
2203             let change_guid = ChangeGuid {
2204                 merged_node: merged_child_node,
2205                 level,
2206             };
2207             ops.change_guids.push(change_guid);
2208         }
2209         let local_child_node = merged_node
2210             .merge_state
2211             .local_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();
2214         if local_child_node
2215             .and_then(|m| merged_local_child_node.map(|n| m.guid != n.guid))
2216             .unwrap_or(true)
2217         {
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,
2225                 position,
2226                 level,
2227             };
2228             ops.apply_new_local_structure
2229                 .push(apply_new_local_structure);
2230         }
2231         let local_needs_merge = merged_child_node
2232             .merge_state
2233             .local_node()
2234             .map(|node| node.needs_merge)
2235             .unwrap_or(false);
2236         let should_upload = merged_child_node.merge_state.should_upload();
2237         match (local_needs_merge, should_upload) {
2238             (false, true) => {
2239                 // Local item isn't flagged for upload, but should be.
2240                 let set_local_unmerged = SetLocalUnmerged {
2241                     merged_node: merged_child_node,
2242                 };
2243                 ops.set_local_unmerged.push(set_local_unmerged);
2244             }
2245             (true, false) => {
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,
2249                 };
2250                 ops.set_local_merged.push(set_local_merged);
2251             }
2252             _ => {}
2253         }
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,
2260             });
2261         }
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);
2270             }
2271         }
2272         accumulate(signal, ops, merged_child_node, level + 1, is_tagging)?;
2273     }
2274     Ok(())
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)