use crate::checksum::Ctx;
use crate::checksum::file::{Checksum, SumsFile};
use crate::cli::CopyMode;
use crate::error::{ApiError, Error};
use crate::task::check::{CheckTask, CheckTaskError, GroupBy};
use crate::task::copy::{CopyTask, CopyTaskError};
use crate::task::generate::{GenerateTask, GenerateTaskError, GenerateTaskResult};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};
use std::time::Duration;
pub type Result<T> = std::result::Result<T, Box<T>>;
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GenerateStats {
pub(crate) elapsed_seconds: f64,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) stats: Vec<GenerateFileStats>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) check_stats: Option<Box<CheckStats>>,
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub(crate) recoverable_errors: HashSet<ApiError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) unrecoverable_error: Option<Error>,
#[serde(skip)]
pub(crate) sums: Option<Vec<(String, SumsFile)>>,
}
impl From<Error> for Box<GenerateStats> {
fn from(err: Error) -> Self {
Box::new(GenerateStats {
unrecoverable_error: Some(err),
..Default::default()
})
}
}
impl From<GenerateTaskError> for Box<GenerateStats> {
fn from(err: GenerateTaskError) -> Self {
let stats = err.error.into();
GenerateStats::default().push_task(err.task);
stats
}
}
impl GenerateStats {
pub fn new(stats: Vec<GenerateFileStats>, check_stats: Option<CheckStats>) -> Self {
let mut result = Self {
elapsed_seconds: 0.0,
..Default::default()
};
stats.into_iter().for_each(|stat| result.push_stats(stat));
result.set_check_stats(check_stats);
result
}
pub fn from_sums(sums: Vec<(String, SumsFile)>) -> Self {
Self {
sums: Some(sums),
..Default::default()
}
}
fn push_stats(&mut self, stats: GenerateFileStats) {
if !stats.checksums_generated.0.is_empty() {
self.stats.push(stats);
}
}
fn push_task(&mut self, task: GenerateTask) {
self.push_stats(GenerateFileStats::from_task(task));
}
pub fn add_stats(mut self, task: GenerateTaskResult) -> Result<Self> {
match task {
Ok(task) => {
self.push_task(task);
Ok(self)
}
Err(err) => {
self.push_task(err.task);
Err(Box::new(self))
}
}
}
pub fn set_sums_files(&mut self, sums: Vec<(String, SumsFile)>) {
self.sums = Some(sums);
}
pub fn set_check_stats(&mut self, check_stats: Option<CheckStats>) {
self.check_stats = check_stats.map(Box::new);
}
pub fn set_recoverable_errors(&mut self, recoverable_errors: HashSet<ApiError>) {
self.recoverable_errors = recoverable_errors;
}
pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
self.elapsed_seconds = elapsed.as_secs_f64();
self
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ChecksumPair {
pub(crate) kind: Ctx,
pub(crate) value: Checksum,
}
impl ChecksumPair {
pub fn new(kind: Ctx, value: Checksum) -> Self {
Self { kind, value }
}
}
impl From<&CheckStats> for Option<ChecksumPair> {
fn from(stats: &CheckStats) -> Self {
stats
.compared
.first()
.map(|compared| compared.reason.clone())
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ChecksumStats(Vec<ChecksumPair>);
impl From<BTreeMap<Ctx, Checksum>> for ChecksumStats {
fn from(map: BTreeMap<Ctx, Checksum>) -> Self {
Self(
map.into_iter()
.map(|(k, v)| ChecksumPair::new(k, v))
.collect(),
)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GenerateFileStats {
pub(crate) input: String,
pub(crate) updated: bool,
pub(crate) checksums_generated: ChecksumStats,
}
impl GenerateFileStats {
pub fn new(input: String, updated: bool, checksums_generated: ChecksumStats) -> Self {
Self {
input,
updated,
checksums_generated,
}
}
pub fn from_task(task: GenerateTask) -> Self {
let (_, object, updated, checksums_generated) = task.into_inner();
Self::new(object.location(), updated, checksums_generated.into())
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct CheckStats {
pub(crate) elapsed_seconds: f64,
pub(crate) comparison_type: GroupBy,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) compared: Vec<CheckComparison>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) groups: Vec<Vec<String>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) updated: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) generate_stats: Option<GenerateStats>,
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub(crate) api_errors: HashSet<ApiError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) unrecoverable_error: Option<Error>,
}
impl From<Error> for Box<CheckStats> {
fn from(err: Error) -> Self {
Box::new(CheckStats {
unrecoverable_error: Some(err),
..Default::default()
})
}
}
impl From<CheckTaskError> for Box<CheckStats> {
fn from(err: CheckTaskError) -> Self {
let mut stats = CheckStats::from_task(err.task, None);
stats.unrecoverable_error = Some(err.error);
Box::new(stats)
}
}
impl CheckStats {
pub fn new(
comparison_type: GroupBy,
compared: Vec<CheckComparison>,
groups: Vec<Vec<String>>,
updated: Vec<String>,
generate_stats: Option<GenerateStats>,
api_errors: HashSet<ApiError>,
) -> Self {
Self {
elapsed_seconds: 0.0,
comparison_type,
compared,
groups,
updated,
generate_stats,
api_errors,
unrecoverable_error: None,
}
}
pub fn from_generate_task(group_by: GroupBy, generate_stats: GenerateStats) -> Self {
Self::new(
group_by,
vec![],
vec![],
vec![],
Some(generate_stats),
Default::default(),
)
}
pub fn from_task(task: CheckTask, generate_stats: Option<GenerateStats>) -> Self {
let group_by = task.group_by();
let (objects, compared, updated, api_errors) = task.into_inner();
Self::new(
group_by,
compared,
objects.to_groups(),
updated,
generate_stats,
api_errors,
)
}
pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
self.elapsed_seconds = elapsed.as_secs_f64();
self
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct CopyStats {
pub(crate) elapsed_seconds: f64,
pub(crate) source: String,
pub(crate) destination: String,
pub(crate) bytes_transferred: u64,
pub(crate) skipped: bool,
pub(crate) sums_mismatch: bool,
pub(crate) copy_mode: CopyMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) reason: Option<ChecksumPair>,
pub(crate) n_retries: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) check_stats: Option<CheckStats>,
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub(crate) api_errors: HashSet<ApiError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) unrecoverable_error: Option<Error>,
}
impl From<Error> for Box<CopyStats> {
fn from(err: Error) -> Self {
Box::new(CopyStats {
unrecoverable_error: Some(err),
..Default::default()
})
}
}
impl From<CopyTaskError> for Box<CopyStats> {
fn from(err: CopyTaskError) -> Self {
let mut stats = CopyStats::from_task(err.task, None, false, false);
stats.unrecoverable_error = Some(err.error);
Box::new(stats)
}
}
impl CopyStats {
pub fn from_check_stats(
source: String,
destination: String,
copy_mode: CopyMode,
check_stats: CheckStats,
skipped: bool,
sums_mismatch: bool,
) -> Self {
Self {
elapsed_seconds: 0.0,
source,
destination,
bytes_transferred: 0,
skipped,
sums_mismatch,
copy_mode,
reason: Option::<ChecksumPair>::from(&check_stats),
n_retries: 0,
api_errors: Default::default(),
check_stats: Some(check_stats),
unrecoverable_error: None,
}
}
pub fn from_task(
copy_task: CopyTask,
check_stats: Option<CheckStats>,
skipped: bool,
sums_mismatch: bool,
) -> Self {
Self {
elapsed_seconds: 0.0,
source: copy_task.source().format(),
destination: copy_task.destination().format(),
bytes_transferred: copy_task.bytes_transferred(),
skipped,
sums_mismatch,
copy_mode: copy_task.copy_mode(),
reason: check_stats.as_ref().and_then(Option::<ChecksumPair>::from),
n_retries: copy_task.n_retries(),
api_errors: copy_task.api_errors(),
check_stats,
unrecoverable_error: None,
}
}
pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
self.elapsed_seconds = elapsed.as_secs_f64();
self
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CheckComparison {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) locations: Vec<String>,
pub(crate) reason: ChecksumPair,
}
impl CheckComparison {
pub fn new(locations: Vec<String>, reason: ChecksumPair) -> Self {
Self { locations, reason }
}
}