1use super::{LibraryId, Media, MediaID, MovieBatchId, MovieReference, Series};
2
3use crate::{
4 SeriesID, SubjectKey,
5 chrono::{DateTime, Utc},
6};
7
8use std::fmt;
9use uuid::Uuid;
10
11#[cfg(feature = "rkyv")]
12use crate::rkyv_wrappers::DateTimeWrapper;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(
17 feature = "rkyv",
18 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
19)]
20#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq, Eq)))]
21pub struct ScanStageLatencySummary {
22 pub scan: u64,
23 pub analyze: u64,
24 pub index: u64,
25}
26
27#[derive(Clone, PartialEq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(
30 feature = "rkyv",
31 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
32)]
33#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
34pub struct ScanProgressEvent {
35 pub version: String,
36 pub scan_id: Uuid,
37 pub library_id: LibraryId,
38 pub status: String,
39 pub completed_items: u64,
40 pub total_items: u64,
41 pub sequence: u64,
42 #[cfg_attr(
43 feature = "serde",
44 serde(skip_serializing_if = "Option::is_none")
45 )]
46 pub current_path: Option<String>,
47 #[cfg_attr(
48 feature = "serde",
49 serde(skip_serializing_if = "Option::is_none")
50 )]
51 pub path_key: Option<SubjectKey>,
52 pub p95_stage_latencies_ms: ScanStageLatencySummary,
53 pub correlation_id: Uuid,
54 pub idempotency_key: String,
55 #[cfg_attr(feature = "rkyv", rkyv(with = DateTimeWrapper))]
56 pub emitted_at: DateTime<Utc>,
57 #[cfg_attr(
58 feature = "serde",
59 serde(skip_serializing_if = "Option::is_none")
60 )]
61 pub retrying_items: Option<u64>,
62 #[cfg_attr(
63 feature = "serde",
64 serde(skip_serializing_if = "Option::is_none")
65 )]
66 pub dead_lettered_items: Option<u64>,
67}
68
69impl fmt::Debug for ScanProgressEvent {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("ScanProgressEvent")
72 .field("scan_id", &self.scan_id)
73 .field("library_id", &self.library_id)
74 .field("status", &self.status)
75 .field("completed_items", &self.completed_items)
76 .field("total_items", &self.total_items)
77 .field("sequence", &self.sequence)
78 .field("current_path", &self.current_path)
79 .field("retrying_items", &self.retrying_items)
80 .field("dead_lettered_items", &self.dead_lettered_items)
81 .field("correlation_id", &self.correlation_id)
82 .field("idempotency_key", &self.idempotency_key)
83 .field("p95_stage_latencies_ms", &self.p95_stage_latencies_ms)
84 .field("emitted_at", &self.emitted_at)
85 .finish()
86 }
87}
88
89#[derive(Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91#[cfg_attr(
92 feature = "rkyv",
93 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
94)]
95#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
96pub struct ScanEventMetadata {
97 pub version: String,
98 pub correlation_id: Uuid,
99 pub idempotency_key: String,
100 pub library_id: LibraryId,
101}
102
103#[derive(Debug, Clone, PartialEq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105#[cfg_attr(
106 feature = "rkyv",
107 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
108)]
109#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
110#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq)))]
111pub enum MediaEvent {
112 MovieAdded {
113 movie: MovieReference,
114 },
115 MovieBatchFinalized {
116 library_id: LibraryId,
117 batch_id: MovieBatchId,
118 },
119 SeriesAdded {
120 series: Series,
121 },
122 SeriesBundleFinalized {
123 library_id: LibraryId,
124 series_id: SeriesID,
125 },
126 MovieUpdated {
127 movie: MovieReference,
128 },
129 SeriesUpdated {
130 series: Series,
131 },
132
133 MediaDeleted {
134 id: MediaID,
135 },
136
137 ScanStarted {
138 scan_id: Uuid,
139 metadata: ScanEventMetadata,
140 },
141 ScanProgress {
142 scan_id: Uuid,
143 progress: ScanProgressEvent,
144 },
145 ScanCompleted {
146 scan_id: Uuid,
147 metadata: ScanEventMetadata,
148 },
149 ScanFailed {
150 scan_id: Uuid,
151 error: String,
152 metadata: ScanEventMetadata,
153 },
154}
155
156impl MediaEvent {
157 pub fn into_media(self) -> Option<Media> {
158 match self {
159 MediaEvent::MovieAdded { movie }
160 | MediaEvent::MovieUpdated { movie } => {
161 Some(Media::Movie(Box::new(movie)))
162 }
163 MediaEvent::MovieBatchFinalized { .. } => None,
164 MediaEvent::SeriesBundleFinalized { .. } => None,
165 MediaEvent::SeriesAdded { series }
166 | MediaEvent::SeriesUpdated { series } => {
167 Some(Media::Series(Box::new(series)))
168 }
169 MediaEvent::MediaDeleted { .. }
170 | MediaEvent::ScanStarted { .. }
171 | MediaEvent::ScanProgress { .. }
172 | MediaEvent::ScanCompleted { .. }
173 | MediaEvent::ScanFailed { .. } => None,
174 }
175 }
176}