dogear/
merge.rs

1// Copyright 2018-2019 Mozilla
2
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.
14
15use std::{
16    collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
17    fmt, mem,
18};
19
20use crate::driver::{AbortSignal, DefaultAbortSignal, DefaultDriver, Driver};
21use crate::error::{ErrorKind, Result};
22use crate::guid::{Guid, IsValidGuid, TAGS_GUID};
23use crate::tree::{Content, MergeState, MergedNode, Node, Tree, Validity};
24
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)]
28enum 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,
35}
36
37/// Records structure change counters for telemetry.
38#[derive(Clone, Copy, Default, Debug, Eq, Hash, PartialEq)]
39pub 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,
53}
54
55/// Holds (matching remote dupes for local GUIDs, matching local dupes for
56/// remote GUIDs).
57type MatchingDupes<'t> = (HashMap<Guid, Node<'t>>, HashMap<Guid, Node<'t>>);
58
59/// Indicates which side to take in case of a merge conflict.
60#[derive(Clone, Copy, Debug)]
61enum ConflictResolution {
62    Local,
63    Remote,
64    Unchanged,
65}
66
67/// A hash key used to match dupes by content.
68#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
69enum 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),
75}
76
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.
99pub 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,
109}
110
111impl<'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    }
126}
127
128impl<'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    }
148
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        };
156
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        }
173
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        }
189
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    }
200
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    }
207
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);
210
211        self.merged_guids.insert(local_node.guid.clone());
212
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        };
230
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        }
245
246        if local_node.diverged() {
247            merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
248        }
249
250        Ok(merged_node)
251    }
252
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);
255
256        self.merged_guids.insert(remote_node.guid.clone());
257
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        }
290
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        }
299
300        Ok(merged_node)
301    }
302
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        );
315
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        }
327
328        self.merged_guids.insert(local_node.guid.clone());
329        self.merged_guids.insert(remote_node.guid.clone());
330
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        };
350
351        let (item, children) = self.resolve_value_conflict(local_node, remote_node);
352
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        );
370
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            }
392
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            }
413
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        }
430
431        if local_node.diverged() {
432            merged_node.merge_state = merged_node.merge_state.with_new_local_structure();
433        }
434
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        }
439
440        Ok(merged_node)
441    }
442
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        );
463
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        }
521
522        Ok(())
523    }
524
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        }
557
558        trace!(
559            self.driver,
560            "Merging remote child {} of {} into {}",
561            remote_child_node,
562            remote_parent_node,
563            merged_node
564        );
565
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        }
583
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");
590
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            );
598
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                );
608
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            }
629
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                    );
648
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                }
655
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            }
706
707            return Ok(());
708        }
709
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        );
718
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    }
746
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        }
769
770        trace!(
771            self.driver,
772            "Merging local child {} of {} into {}",
773            local_child_node,
774            local_parent_node,
775            merged_node
776        );
777
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        }
791
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");
800
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            );
808
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                );
818
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            }
841
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                        );
861
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                        );
885
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                        }
904
905                        merged_node.merged_children.push(merged_child_node);
906                        self.structure_counts.merged_nodes += 1;
907                    }
908                }
909
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            }
936
937            return Ok(());
938        }
939
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        );
948
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    }
990
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        }
1002
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            }
1049
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            }
1065
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            }
1088
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    }
1110
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        }
1123
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);
1133
1134                if latest_local_age < latest_remote_age {
1135                    ConflictResolution::Local
1136                } else {
1137                    ConflictResolution::Remote
1138                }
1139            }
1140
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,
1145
1146            (false, false) => ConflictResolution::Unchanged,
1147        }
1148    }
1149
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        }
1171
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        }
1207
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        }
1213
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        }
1218
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        }
1251
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    }
1256
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        }
1278
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        }
1320
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        }
1326
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        }
1331
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        }
1357
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    }
1362
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                    );
1402
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    }
1426
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                    );
1464
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    }
1488
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();
1515
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        }
1559
1560        let mut local_to_remote = HashMap::new();
1561        let mut remote_to_local = HashMap::new();
1562
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        }
1640
1641        Ok((local_to_remote, remote_to_local))
1642    }
1643
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    }
1699
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    }
1755}
1756
1757/// The root of a merged tree, from which all merged nodes descend.
1758#[derive(Debug)]
1759pub 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,
1767}
1768
1769impl<'t> MergedRoot<'t> {
1770    /// Returns the root node.
1771    #[inline]
1772    pub fn node(&self) -> &MergedNode<'_> {
1773        &self.node
1774    }
1775
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)?;
1785
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        }
1822
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        }
1870
1871        Ok(ops)
1872    }
1873
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    }
1880
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    }
1886
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    }
1893
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    }
1900
1901    /// Returns structure change counts for this merged root.
1902    #[inline]
1903    pub fn counts(&self) -> &StructureCounts {
1904        &self.structure_counts
1905    }
1906}
1907
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)]
1912pub 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>>,
1924}
1925
1926impl<'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    }
1942
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    }
1959}
1960
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)]
1965pub 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,
1972}
1973
1974impl<'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    }
1984}
1985
1986impl<'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    }
1995}
1996
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)]
2000pub struct ApplyRemoteItem<'t> {
2001    pub merged_node: &'t MergedNode<'t>,
2002    pub level: usize,
2003}
2004
2005impl<'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    }
2016}
2017
2018impl<'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    }
2031}
2032
2033/// A completion op to update the parent and position of a local item.
2034#[derive(Clone, Copy, Debug)]
2035pub 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,
2040}
2041
2042impl<'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    }
2050}
2051
2052/// A completion op to flag a local item for upload.
2053#[derive(Clone, Copy, Debug)]
2054pub struct SetLocalUnmerged<'t> {
2055    pub merged_node: &'t MergedNode<'t>,
2056}
2057
2058impl<'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    }
2062}
2063
2064/// A completion op to skip uploading a local item after resolving merge
2065/// conflicts.
2066#[derive(Clone, Copy, Debug)]
2067pub struct SetLocalMerged<'t> {
2068    pub merged_node: &'t MergedNode<'t>,
2069}
2070
2071impl<'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    }
2075}
2076
2077/// A completion op to upload or reupload a merged item.
2078#[derive(Clone, Copy, Debug)]
2079pub struct UploadItem<'t> {
2080    pub merged_node: &'t MergedNode<'t>,
2081}
2082
2083impl<'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    }
2087}
2088
2089/// A completion op to upload a tombstone.
2090#[derive(Clone, Copy, Debug)]
2091pub struct UploadTombstone<'t>(&'t Guid);
2092
2093impl<'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    }
2099}
2100
2101impl<'t> fmt::Display for UploadTombstone<'t> {
2102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2103        write!(f, "Upload tombstone {}", self.0)
2104    }
2105}
2106
2107/// A completion op to flag a remote item as merged.
2108#[derive(Clone, Copy, Debug)]
2109pub struct SetRemoteMerged<'t>(&'t Guid);
2110
2111impl<'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    }
2117}
2118
2119impl<'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    }
2123}
2124
2125/// A completion op to store a tombstone for a remote item.
2126#[derive(Clone, Copy, Debug)]
2127pub struct InsertLocalTombstone<'t>(Node<'t>);
2128
2129impl<'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    }
2135}
2136
2137impl<'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    }
2141}
2142
2143/// A completion op to delete a local tombstone.
2144#[derive(Clone, Copy, Debug)]
2145pub struct DeleteLocalTombstone<'t>(&'t Guid);
2146
2147impl<'t> DeleteLocalTombstone<'t> {
2148    /// Returns the GUID of the tombstone.
2149    #[inline]
2150    pub fn guid(self) -> &'t Guid {
2151        self.0
2152    }
2153}
2154
2155impl<'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    }
2159}
2160
2161/// A completion op to delete an item from the local tree.
2162#[derive(Clone, Copy, Debug)]
2163pub struct DeleteLocalItem<'t>(Node<'t>);
2164
2165impl<'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    }
2171}
2172
2173impl<'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    }
2177}
2178
2179/// Recursively accumulates completion ops, starting at `merged_node` and
2180/// drilling down into all its descendants.
2181fn 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(())
2275}
2276
2277/// Converts all items in the list to strings.
2278pub(crate) fn to_strings<'a, T: ToString>(items: &'a [T]) -> impl Iterator<Item = String> + 'a {
2279    items.iter().map(ToString::to_string)
2280}