use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use super::location::LocationId;
use super::retry::RetryPolicy;
use super::transfer::{Transfer, TransferKind, TransferState};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PresenceState {
Present,
Pending,
Syncing,
Failed,
Absent,
}
impl PresenceState {
pub fn as_str(&self) -> &'static str {
match self {
Self::Present => "present",
Self::Pending => "pending",
Self::Syncing => "syncing",
Self::Failed => "failed",
Self::Absent => "absent",
}
}
pub fn priority(&self) -> u8 {
match self {
Self::Absent => 0,
Self::Present => 1,
Self::Pending => 2,
Self::Syncing => 3,
Self::Failed => 4,
}
}
pub fn from_transfer(transfer: &Transfer, policy: &RetryPolicy) -> Self {
match transfer.state() {
TransferState::Blocked => Self::Pending,
TransferState::Queued => Self::Pending,
TransferState::InFlight => Self::Syncing,
TransferState::Completed => Self::Present,
TransferState::Failed => {
if transfer.is_retryable(policy) {
Self::Pending
} else {
Self::Failed
}
}
TransferState::Cancelled => Self::Absent,
}
}
}
impl fmt::Display for PresenceState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PresenceView {
pub location: LocationId,
pub state: PresenceState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub synced_at: Option<DateTime<Utc>>,
pub attempt: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorEntry {
pub file_id: String,
pub src: LocationId,
pub dest: LocationId,
pub error: String,
pub attempts: u32,
}
impl ErrorEntry {
pub(crate) fn from_transfer(t: &Transfer) -> Self {
Self {
file_id: t.file_id().to_string(),
src: t.src().clone(),
dest: t.dest().clone(),
error: t.error().unwrap_or("unknown error").to_string(),
attempts: t.attempt(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingEntry {
pub file_id: String,
pub src: LocationId,
pub dest: LocationId,
pub kind: TransferKind,
}
impl PendingEntry {
pub(crate) fn from_transfer(t: &Transfer) -> Self {
Self {
file_id: t.file_id().to_string(),
src: t.src().clone(),
dest: t.dest().clone(),
kind: t.kind(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::retry::TransferErrorKind;
fn loc(s: &str) -> LocationId {
LocationId::new(s).unwrap()
}
fn make_transfer(
state: TransferState,
error_kind: Option<TransferErrorKind>,
attempt: u32,
) -> Transfer {
Transfer::reconstitute(
"t-1".into(),
"f-1".into(),
loc("local"),
loc("cloud"),
TransferKind::Sync,
state,
if state == TransferState::Failed {
Some("err".into())
} else {
None
},
error_kind,
attempt,
Utc::now(),
if state != TransferState::Queued {
Some(Utc::now())
} else {
None
},
if matches!(state, TransferState::Completed | TransferState::Failed) {
Some(Utc::now())
} else {
None
},
)
}
#[test]
fn presence_from_queued() {
let t = make_transfer(TransferState::Queued, None, 1);
assert_eq!(
PresenceState::from_transfer(&t, &RetryPolicy::default()),
PresenceState::Pending
);
}
#[test]
fn presence_from_completed() {
let t = make_transfer(TransferState::Completed, None, 1);
assert_eq!(
PresenceState::from_transfer(&t, &RetryPolicy::default()),
PresenceState::Present
);
}
#[test]
fn presence_from_in_flight() {
let t = make_transfer(TransferState::InFlight, None, 1);
assert_eq!(
PresenceState::from_transfer(&t, &RetryPolicy::default()),
PresenceState::Syncing
);
}
#[test]
fn presence_from_failed_transient_retryable() {
let policy = RetryPolicy::new(3);
let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Transient), 1);
assert_eq!(
PresenceState::from_transfer(&t, &policy),
PresenceState::Pending
);
}
#[test]
fn presence_from_failed_transient_exhausted() {
let policy = RetryPolicy::new(3);
let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Transient), 3);
assert_eq!(
PresenceState::from_transfer(&t, &policy),
PresenceState::Failed
);
}
#[test]
fn presence_from_failed_permanent() {
let policy = RetryPolicy::new(10);
let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Permanent), 1);
assert_eq!(
PresenceState::from_transfer(&t, &policy),
PresenceState::Failed
);
}
}