1use std::path::PathBuf;
2
3use uuid::Uuid;
4
5use super::LibraryId;
6use crate::chrono::{DateTime, Utc};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct ScanRequest {
11 pub library_id: LibraryId,
12 pub force_refresh: bool,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct ScanResponse {
18 pub status: ScanStatus,
19 pub scan_id: Option<Uuid>,
20 pub message: String,
21}
22
23impl ScanResponse {
24 pub fn new(
25 status: ScanStatus,
26 scan_id: Option<Uuid>,
27 message: String,
28 ) -> Self {
29 ScanResponse {
30 status,
31 scan_id,
32 message,
33 }
34 }
35
36 pub fn new_scan_started(scan_id: Uuid, message: String) -> Self {
37 ScanResponse {
38 status: ScanStatus::Scanning,
39 scan_id: Some(scan_id),
40 message,
41 }
42 }
43
44 pub fn new_failed(message: String) -> Self {
45 ScanResponse {
46 status: ScanStatus::Failed,
47 scan_id: None,
48 message,
49 }
50 }
51
52 pub fn new_canceled(scan_id: Uuid) -> Self {
53 ScanResponse {
54 status: ScanStatus::Cancelled,
55 scan_id: Some(scan_id),
56 message: "Scan canceled".to_string(),
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct ScanProgress {
64 pub scan_id: Uuid,
65 pub status: ScanStatus,
66 pub paths: Vec<PathBuf>,
67 pub library_names: Vec<String>,
68 pub library_ids: Vec<String>,
69 pub folders_to_scan: usize,
70 pub folders_scanned: usize,
71 pub movies_scanned: usize,
72 pub series_scanned: usize,
73 pub seasons_scanned: usize,
74 pub episodes_scanned: usize,
75 pub skipped_samples: usize,
76 pub errors: Vec<String>,
77 pub current_media: Option<String>,
78 pub current_library: Option<String>,
79 pub started_at: DateTime<Utc>,
80 pub completed_at: Option<DateTime<Utc>>,
81 pub estimated_time_remaining: Option<std::time::Duration>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub enum ScanStatus {
87 Pending,
88 Scanning,
89 Completed,
90 Failed,
91 Cancelled,
92}
93
94pub mod scanner {
95 pub mod settings {
96 pub const DEFAULT_VIDEO_FILE_EXTENSIONS: &[&str] = &[
98 "mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v", "mpg",
99 "mpeg",
100 ];
101
102 pub fn default_video_file_extensions_vec() -> Vec<String> {
104 DEFAULT_VIDEO_FILE_EXTENSIONS
105 .iter()
106 .map(|ext| ext.to_string())
107 .collect()
108 }
109 }
110}
111
112pub mod orchestration {
113 pub mod budget {
114 #[cfg(feature = "serde")]
115 use serde::{Deserialize, Serialize};
116
117 #[derive(Clone, Debug)]
119 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
120 pub struct BudgetConfig {
121 pub library_scan_limit: usize,
123 pub media_analysis_limit: usize,
125 pub metadata_limit: usize,
127 pub indexing_limit: usize,
129 pub image_fetch_limit: usize,
131 }
132
133 impl Default for BudgetConfig {
134 fn default() -> Self {
135 let cpu_count =
136 std::thread::available_parallelism().map_or(1, |n| n.get());
137 Self {
138 library_scan_limit: 1,
139 media_analysis_limit: 4,
140 metadata_limit: cpu_count * 2,
141 indexing_limit: cpu_count,
142 image_fetch_limit: 4,
143 }
144 }
145 }
146 }
147
148 pub mod config {
149 use std::collections::HashMap;
150
151 use crate::ids::LibraryId;
152
153 #[cfg(feature = "serde")]
154 use serde::{Deserialize, Serialize};
155
156 #[derive(Clone, Debug, Default)]
161 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
162 pub struct OrchestratorConfig {
163 pub queue: QueueConfig,
165 pub priority_weights: PriorityWeights,
167 pub retry: RetryConfig,
169 pub metadata_limits: MetadataLimits,
171 pub bulk_mode: BulkModeTuning,
173 pub lease: LeaseConfig,
175 pub budget: super::budget::BudgetConfig,
177 pub watch: WatchConfig,
179 }
180
181 #[derive(Clone, Debug)]
182 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
183 pub struct QueueConfig {
184 pub max_parallel_scans: usize,
186 pub max_parallel_series_resolve: usize,
187 pub max_parallel_analyses: usize,
188 pub max_parallel_metadata: usize,
189 pub max_parallel_index: usize,
190 pub max_parallel_image_fetch: usize,
191 pub max_parallel_scans_per_device: usize,
193 pub high_watermark: usize,
195 pub critical_watermark: usize,
197 pub coalesce_window_ms: u64,
199 pub default_library_cap: usize,
201 pub default_library_weight: u32,
203 #[cfg_attr(feature = "serde", serde(default))]
205 pub library_overrides: HashMap<LibraryId, LibraryQueuePolicy>,
206 }
207
208 impl Default for QueueConfig {
209 fn default() -> Self {
210 Self {
211 max_parallel_scans: 6,
212 max_parallel_series_resolve: 2,
213 max_parallel_analyses: 2,
214 max_parallel_metadata: 4,
215 max_parallel_index: 1,
216 max_parallel_image_fetch: 4,
217 max_parallel_scans_per_device: 16,
218 high_watermark: 10_000,
219 critical_watermark: 20_000,
220 coalesce_window_ms: 100,
221 default_library_cap: 32,
222 default_library_weight: 1,
223 library_overrides: HashMap::new(),
224 }
225 }
226 }
227
228 #[derive(Clone, Debug, Default)]
230 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
231 pub struct LibraryQueuePolicy {
232 pub max_inflight: Option<usize>,
234 pub weight: Option<u32>,
236 }
237
238 #[derive(Clone, Copy, Debug)]
240 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241 pub struct LeaseConfig {
242 pub lease_ttl_secs: i64,
244 pub renew_at_fraction: f32,
246 pub renew_min_margin_ms: u64,
248 pub housekeeper_interval_ms: u64,
250 }
251
252 impl Default for LeaseConfig {
253 fn default() -> Self {
254 Self {
255 lease_ttl_secs: 30,
256 renew_at_fraction: 0.5,
257 renew_min_margin_ms: 2_000,
258 housekeeper_interval_ms: 15_000,
259 }
260 }
261 }
262
263 #[derive(Clone, Copy, Debug)]
264 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
265 pub struct PriorityWeights {
266 pub p0: u8,
267 pub p1: u8,
268 pub p2: u8,
269 pub p3: u8,
270 }
271
272 impl Default for PriorityWeights {
273 fn default() -> Self {
274 Self {
275 p0: 8,
276 p1: 4,
277 p2: 2,
278 p3: 1,
279 }
280 }
281 }
282
283 #[derive(Clone, Copy, Debug)]
284 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
285 pub struct RetryConfig {
286 pub max_attempts: u16,
287 pub backoff_base_ms: u64,
288 pub backoff_max_ms: u64,
289 pub fast_retry_attempts: u16,
291 pub fast_retry_factor: f32,
293 pub heavy_library_attempt_threshold: u16,
295 pub heavy_library_slowdown_factor: f32,
297 pub jitter_ratio: f32,
299 pub jitter_min_ms: u64,
301 }
302
303 impl RetryConfig {
304 pub fn backoff_base(&self) -> core::time::Duration {
305 core::time::Duration::from_millis(self.backoff_base_ms)
306 }
307
308 pub fn backoff_max(&self) -> core::time::Duration {
309 core::time::Duration::from_millis(self.backoff_max_ms)
310 }
311 }
312
313 impl Default for RetryConfig {
314 fn default() -> Self {
315 Self {
316 max_attempts: 5,
317 backoff_base_ms: 2_000,
318 backoff_max_ms: 5 * 60 * 1_000,
319 fast_retry_attempts: 2,
320 fast_retry_factor: 0.35,
321 heavy_library_attempt_threshold: 4,
322 heavy_library_slowdown_factor: 1.8,
323 jitter_ratio: 0.25,
324 jitter_min_ms: 250,
325 }
326 }
327 }
328
329 #[derive(Clone, Copy, Debug)]
330 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
331 pub struct MetadataLimits {
332 pub max_concurrency: usize,
333 pub max_qps: u32,
334 }
335
336 impl Default for MetadataLimits {
337 fn default() -> Self {
338 Self {
339 max_concurrency: 2,
340 max_qps: 100,
341 }
342 }
343 }
344
345 #[derive(Clone, Debug)]
346 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
347 pub struct BulkModeTuning {
348 pub speedup_factor: f32,
349 pub maintenance_partition_count: usize,
350 }
351
352 impl Default for BulkModeTuning {
353 fn default() -> Self {
354 Self {
355 speedup_factor: 1.2,
356 maintenance_partition_count: 8,
357 }
358 }
359 }
360
361 #[derive(Clone, Debug)]
363 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
364 pub struct WatchConfig {
365 pub debounce_window_ms: u64,
367 pub max_batch_events: usize,
369 #[cfg_attr(
371 feature = "serde",
372 serde(default = "WatchConfig::default_poll_interval_ms")
373 )]
374 pub poll_interval_ms: u64,
375 }
376
377 impl Default for WatchConfig {
378 fn default() -> Self {
379 Self {
380 debounce_window_ms: 250,
381 max_batch_events: 8192,
382 poll_interval_ms: Self::default_poll_interval_ms(),
383 }
384 }
385 }
386
387 impl WatchConfig {
388 const fn default_poll_interval_ms() -> u64 {
389 30_000
390 }
391 }
392 }
393}