ferrex-model 0.1.2-alpha

Shared data models for the Ferrex media platform
Documentation
use super::{LibraryId, Media, MediaID, MovieBatchId, MovieReference, Series};

use crate::{
    SeriesID, SubjectKey,
    chrono::{DateTime, Utc},
};

use std::fmt;
use uuid::Uuid;

#[cfg(feature = "rkyv")]
use crate::rkyv_wrappers::DateTimeWrapper;

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq, Eq)))]
pub struct ScanStageLatencySummary {
    pub scan: u64,
    pub analyze: u64,
    pub index: u64,
}

#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
pub struct ScanProgressEvent {
    pub version: String,
    pub scan_id: Uuid,
    pub library_id: LibraryId,
    pub status: String,
    pub completed_items: u64,
    pub total_items: u64,
    pub sequence: u64,
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none")
    )]
    pub current_path: Option<String>,
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none")
    )]
    pub path_key: Option<SubjectKey>,
    pub p95_stage_latencies_ms: ScanStageLatencySummary,
    pub correlation_id: Uuid,
    pub idempotency_key: String,
    #[cfg_attr(feature = "rkyv", rkyv(with = DateTimeWrapper))]
    pub emitted_at: DateTime<Utc>,
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none")
    )]
    pub retrying_items: Option<u64>,
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none")
    )]
    pub dead_lettered_items: Option<u64>,
}

impl fmt::Debug for ScanProgressEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ScanProgressEvent")
            .field("scan_id", &self.scan_id)
            .field("library_id", &self.library_id)
            .field("status", &self.status)
            .field("completed_items", &self.completed_items)
            .field("total_items", &self.total_items)
            .field("sequence", &self.sequence)
            .field("current_path", &self.current_path)
            .field("retrying_items", &self.retrying_items)
            .field("dead_lettered_items", &self.dead_lettered_items)
            .field("correlation_id", &self.correlation_id)
            .field("idempotency_key", &self.idempotency_key)
            .field("p95_stage_latencies_ms", &self.p95_stage_latencies_ms)
            .field("emitted_at", &self.emitted_at)
            .finish()
    }
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
pub struct ScanEventMetadata {
    pub version: String,
    pub correlation_id: Uuid,
    pub idempotency_key: String,
    pub library_id: LibraryId,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
pub enum MediaEvent {
    MovieAdded {
        movie: MovieReference,
    },
    MovieBatchFinalized {
        library_id: LibraryId,
        batch_id: MovieBatchId,
    },
    SeriesAdded {
        series: Series,
    },
    SeriesBundleFinalized {
        library_id: LibraryId,
        series_id: SeriesID,
    },
    MovieUpdated {
        movie: MovieReference,
    },
    SeriesUpdated {
        series: Series,
    },

    MediaDeleted {
        id: MediaID,
    },

    ScanStarted {
        scan_id: Uuid,
        metadata: ScanEventMetadata,
    },
    ScanProgress {
        scan_id: Uuid,
        progress: ScanProgressEvent,
    },
    ScanCompleted {
        scan_id: Uuid,
        metadata: ScanEventMetadata,
    },
    ScanFailed {
        scan_id: Uuid,
        error: String,
        metadata: ScanEventMetadata,
    },
}

impl MediaEvent {
    pub fn into_media(self) -> Option<Media> {
        match self {
            MediaEvent::MovieAdded { movie }
            | MediaEvent::MovieUpdated { movie } => {
                Some(Media::Movie(Box::new(movie)))
            }
            MediaEvent::MovieBatchFinalized { .. } => None,
            MediaEvent::SeriesBundleFinalized { .. } => None,
            MediaEvent::SeriesAdded { series }
            | MediaEvent::SeriesUpdated { series } => {
                Some(Media::Series(Box::new(series)))
            }
            MediaEvent::MediaDeleted { .. }
            | MediaEvent::ScanStarted { .. }
            | MediaEvent::ScanProgress { .. }
            | MediaEvent::ScanCompleted { .. }
            | MediaEvent::ScanFailed { .. } => None,
        }
    }
}