1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2018-2019 Mozilla

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{time::Duration, time::Instant};

use crate::driver::{
    AbortSignal, DefaultAbortSignal, DefaultDriver, Driver, TelemetryEvent, TreeStats,
};
use crate::error::Error;
use crate::merge::{MergedRoot, Merger};
use crate::tree::Tree;

/// A store is the main interface to Dogear. It implements methods for building
/// local and remote trees from a storage backend, fetching content info for
/// matching items with similar contents, and persisting the merged tree.
pub trait Store {
    /// The type returned from a successful merge.
    type Ok;

    /// The type returned in the event of a store error.
    type Error: From<Error>;

    /// Builds a fully rooted, consistent tree from the items and tombstones in
    /// the local store.
    fn fetch_local_tree(&self) -> Result<Tree, Self::Error>;

    /// Builds a fully rooted, consistent tree from the items and tombstones in
    /// the mirror.
    fn fetch_remote_tree(&self) -> Result<Tree, Self::Error>;

    /// Applies the merged root to the local store, and stages items for
    /// upload. On Desktop, this method inserts the merged tree into a temp
    /// table, updates Places, and inserts outgoing items into another
    /// temp table.
    fn apply<'t>(&mut self, root: MergedRoot<'t>) -> Result<Self::Ok, Self::Error>;

    /// Builds and applies a merged tree using the default merge driver.
    fn merge(&mut self) -> Result<Self::Ok, Self::Error> {
        self.merge_with_driver(&DefaultDriver, &DefaultAbortSignal)
    }

    /// Builds a complete merged tree from the local and remote trees, resolves
    /// conflicts, dedupes local items, and applies the merged tree using the
    /// given driver.
    fn merge_with_driver(
        &mut self,
        driver: &impl Driver,
        signal: &impl AbortSignal,
    ) -> Result<Self::Ok, Self::Error> {
        signal.err_if_aborted()?;
        debug!(driver, "Building local tree");
        let (local_tree, time) = with_timing(|| self.fetch_local_tree())?;
        driver.record_telemetry_event(TelemetryEvent::FetchLocalTree(TreeStats {
            items: local_tree.size(),
            problems: local_tree.problems().counts(),
            time,
        }));
        trace!(driver, "Built local tree from mirror\n{}", local_tree);

        signal.err_if_aborted()?;
        debug!(driver, "Building remote tree");
        let (remote_tree, time) = with_timing(|| self.fetch_remote_tree())?;
        driver.record_telemetry_event(TelemetryEvent::FetchRemoteTree(TreeStats {
            items: remote_tree.size(),
            problems: remote_tree.problems().counts(),
            time,
        }));
        trace!(driver, "Built remote tree from mirror\n{}", remote_tree);

        signal.err_if_aborted()?;
        debug!(driver, "Building merged tree");
        let merger = Merger::with_driver(driver, signal, &local_tree, &remote_tree);
        let (merged_root, time) = with_timing(|| merger.merge())?;
        driver.record_telemetry_event(TelemetryEvent::Merge(time, *merged_root.counts()));
        trace!(
            driver,
            "Built new merged tree\n{}\nDelete Locally: [{}]\nDelete Remotely: [{}]",
            merged_root.node().to_ascii_string(),
            merged_root
                .local_deletions()
                .map(|d| d.guid.as_str())
                .collect::<Vec<_>>()
                .join(", "),
            merged_root
                .remote_deletions()
                .map(|d| d.guid.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        );

        signal.err_if_aborted()?;
        debug!(driver, "Applying merged tree");
        let (result, time) = with_timing(|| self.apply(merged_root))?;
        driver.record_telemetry_event(TelemetryEvent::Apply(time));

        Ok(result)
    }
}

fn with_timing<T, E>(run: impl FnOnce() -> Result<T, E>) -> Result<(T, Duration), E> {
    let now = Instant::now();
    run().map(|value| (value, now.elapsed()))
}