Source code
Revision control
Copy as Markdown
Other Tools
// 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
//
//
// 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
///
/// 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<Guid> {
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<D: Driver>(
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!(),
);
}
}
}