// 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::{fmt::Arguments, time::Duration}; use log::{Level, LevelFilter, Log}; use crate::error::{ErrorKind, Result}; use crate::guid::Guid; use crate::merge::StructureCounts; use crate::tree::ProblemCounts; /// An abort signal is used to abort merging. Implementations of `AbortSignal` /// can store an aborted flag, usually as an atomic integer or Boolean, set /// the flag on abort, and have `AbortSignal::aborted` return the flag's value. /// /// Since merging is synchronous, it's not possible to interrupt a merge from /// the same thread that started it. In practice, this means a signal will /// implement `Send` and `Sync`, too, so that another thread can set the /// aborted flag. /// /// The name comes from the `AbortSignal` DOM API. pub trait AbortSignal { /// Indicates if the caller signaled to abort. fn aborted(&self) -> bool; /// Returns an error if the caller signaled to abort. This helper makes it /// easier to use the signal with the `?` operator. fn err_if_aborted(&self) -> Result<()> { if self.aborted() { Err(ErrorKind::Abort.into()) } else { Ok(()) } } } /// A default signal that can't be aborted. pub struct DefaultAbortSignal; impl AbortSignal for DefaultAbortSignal { fn aborted(&self) -> bool { false } } /// A merge telemetry event. pub enum TelemetryEvent { FetchLocalTree(TreeStats), FetchRemoteTree(TreeStats), Merge(Duration, StructureCounts), Apply(Duration), } /// Records the time taken to build a local or remote tree, number of items /// in the tree, and structure problem counts. pub struct TreeStats { pub time: Duration, pub items: usize, pub deletions: usize, pub problems: ProblemCounts, } /// A merge driver provides methods to customize merging behavior. pub trait Driver { /// Generates a new GUID for the given invalid GUID. This is used to fix up /// items with GUIDs that Places can't store (bug 1380606, bug 1313026). /// /// The default implementation returns an error, forbidding invalid GUIDs. /// /// Implementations of `Driver` can either use the `rand` and `base64` /// crates to generate a new, random GUID (9 bytes, Base64url-encoded /// without padding), or use an existing method like Desktop's /// `nsINavHistoryService::MakeGuid`. Dogear doesn't generate new GUIDs /// automatically to avoid depending on those crates. /// /// Implementations can also return `Ok(invalid_guid.clone())` to pass /// through all invalid GUIDs, as the tests do. fn generate_new_guid(&self, invalid_guid: &Guid) -> Result { Err(ErrorKind::InvalidGuid(invalid_guid.clone()).into()) } /// Returns the maximum log level for merge messages. The default /// implementation returns the `log` crate's global maximum level. fn max_log_level(&self) -> LevelFilter { log::max_level() } /// Returns a logger for merge messages. /// /// The default implementation returns the `log` crate's global logger. /// /// Implementations can override this method to return a custom logger, /// where using the global logger won't work. For example, Firefox Desktop /// has an existing Sync logging setup outside of the `log` crate. fn logger(&self) -> &dyn Log { log::logger() } /// Records a merge telemetry event. /// /// The default implementation is a no-op that discards the event. /// Implementations can override this method to capture event and bookmark /// validation telemetry. fn record_telemetry_event(&self, _: TelemetryEvent) {} } /// A default implementation of the merge driver. pub struct DefaultDriver; impl Driver for DefaultDriver {} /// Logs a merge message. pub fn log( driver: &D, level: Level, args: Arguments<'_>, module_path: &'static str, file: &'static str, line: u32, ) { let meta = log::Metadata::builder() .level(level) .target(module_path) .build(); if driver.logger().enabled(&meta) { driver.logger().log( &log::Record::builder() .args(args) .metadata(meta) .module_path(Some(module_path)) .file(Some(file)) .line(Some(line)) .build(), ); } } #[macro_export] macro_rules! error { ($driver:expr, $($args:tt)+) => { if log::Level::Error <= $crate::Driver::max_log_level($driver) { $crate::log( $driver, log::Level::Error, format_args!($($args)+), module_path!(), file!(), line!(), ); } } } #[macro_export] macro_rules! warn { ($driver:expr, $($args:tt)+) => { if log::Level::Warn <= $crate::Driver::max_log_level($driver) { $crate::log( $driver, log::Level::Warn, format_args!($($args)+), module_path!(), file!(), line!(), ); } } } #[macro_export] macro_rules! debug { ($driver:expr, $($args:tt)+) => { if log::Level::Debug <= $crate::Driver::max_log_level($driver) { $crate::log( $driver, log::Level::Debug, format_args!($($args)+), module_path!(), file!(), line!(), ); } } } #[macro_export] macro_rules! trace { ($driver:expr, $($args:tt)+) => { if log::Level::Trace <= $crate::Driver::max_log_level($driver) { $crate::log( $driver, log::Level::Trace, format_args!($($args)+), module_path!(), file!(), line!(), ); } } }