1use std::path::PathBuf;
2
3use crate::chrono::{DateTime, Utc};
4use crate::media::Media;
5
6use super::ids::{LibraryId, MovieReferenceBatchSize};
7
8pub trait LibraryLike {
10 fn needs_scan(&self) -> bool;
11 fn get_id(&self) -> LibraryId;
12 fn get_name(&self) -> &str;
13 fn get_type(&self) -> LibraryType;
14 fn get_paths(&self) -> Vec<PathBuf>; fn get_scan_interval(&self) -> u32;
16 fn get_last_scan(&self) -> Option<DateTime<Utc>>;
17 fn is_enabled(&self) -> bool;
18 fn is_auto_scan(&self) -> bool;
19 fn is_watch_for_changes(&self) -> bool;
20 fn is_analyze_on_scan(&self) -> bool;
21 fn get_max_retry_attempts(&self) -> u32;
22 fn get_movie_ref_batch_size(&self) -> MovieReferenceBatchSize;
23 fn get_created_at(&self) -> DateTime<Utc>;
24 fn get_updated_at(&self) -> DateTime<Utc>;
25 fn get_media_references_clone(&self) -> Option<Vec<Media>>;
26}
27
28pub trait LibraryLikeMut: LibraryLike {
30 fn new(
31 name: String,
32 library_type: LibraryType,
33 paths: Vec<PathBuf>,
34 ) -> Self;
35 fn update_last_scan(&mut self);
36 fn set_paths(&mut self, paths: Vec<PathBuf>);
37 fn set_scan_interval(&mut self, interval: u32);
38 fn set_last_scan(&mut self, last_scan: Option<DateTime<Utc>>);
39 fn set_auto_scan(&mut self, auto_scan: bool);
40 fn set_max_retry_attempts(&mut self, max_retry_attempts: u32);
41 fn set_updated_at(&mut self, updated_at: Option<DateTime<Utc>>);
42 fn set_media_references(&mut self, media_references: Vec<Media>);
43}
44
45#[derive(Debug, Clone)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[cfg_attr(
49 feature = "rkyv",
50 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
51)]
52#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, PartialEq, Eq)))]
53pub struct Library {
54 pub id: LibraryId,
55 pub name: String,
56 pub library_type: LibraryType,
57 #[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::VecPathBuf))]
58 pub paths: Vec<PathBuf>,
59 pub scan_interval_minutes: u32,
60 #[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::OptionDateTime))]
61 pub last_scan: Option<DateTime<Utc>>,
62 pub enabled: bool,
63 pub auto_scan: bool,
64 pub watch_for_changes: bool,
65 pub analyze_on_scan: bool,
66 pub max_retry_attempts: u32,
67 pub movie_ref_batch_size: MovieReferenceBatchSize,
68 #[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::DateTimeWrapper))]
69 pub created_at: DateTime<Utc>,
70 #[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::DateTimeWrapper))]
71 pub updated_at: DateTime<Utc>,
72 pub media: Option<Vec<Media>>,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78#[cfg_attr(
79 feature = "rkyv",
80 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
81)]
82#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
83#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Clone, PartialEq, Eq, Hash)))]
84pub enum LibraryType {
85 Movies,
86 Series,
87}
88
89impl std::fmt::Display for LibraryType {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 LibraryType::Movies => write!(f, "Movies"),
93 LibraryType::Series => write!(f, "TV Shows"),
94 }
95 }
96}
97
98impl LibraryLike for Library {
99 fn needs_scan(&self) -> bool {
100 if !self.enabled {
101 return false;
102 }
103
104 match self.last_scan {
105 None => true,
106 Some(last_scan) => {
107 let elapsed = Utc::now().signed_duration_since(last_scan);
108 elapsed.num_minutes() >= self.scan_interval_minutes as i64
109 }
110 }
111 }
112
113 fn get_id(&self) -> LibraryId {
114 self.id
115 }
116
117 fn get_name(&self) -> &str {
118 &self.name
119 }
120
121 fn get_type(&self) -> LibraryType {
122 self.library_type
123 }
124
125 fn get_paths(&self) -> Vec<PathBuf> {
126 self.paths.clone()
127 }
128
129 fn get_scan_interval(&self) -> u32 {
130 self.scan_interval_minutes
131 }
132
133 fn get_last_scan(&self) -> Option<DateTime<Utc>> {
134 self.last_scan
135 }
136
137 fn is_enabled(&self) -> bool {
138 self.enabled
139 }
140
141 fn is_auto_scan(&self) -> bool {
142 self.auto_scan
143 }
144
145 fn is_watch_for_changes(&self) -> bool {
146 self.watch_for_changes
147 }
148
149 fn is_analyze_on_scan(&self) -> bool {
150 self.analyze_on_scan
151 }
152
153 fn get_max_retry_attempts(&self) -> u32 {
154 self.max_retry_attempts
155 }
156
157 fn get_movie_ref_batch_size(&self) -> MovieReferenceBatchSize {
158 self.movie_ref_batch_size
159 }
160
161 fn get_created_at(&self) -> DateTime<Utc> {
162 self.created_at
163 }
164
165 fn get_updated_at(&self) -> DateTime<Utc> {
166 self.updated_at
167 }
168
169 fn get_media_references_clone(&self) -> Option<Vec<Media>> {
170 self.media.clone()
171 }
172}
173
174impl LibraryLikeMut for Library {
175 fn new(
176 name: String,
177 library_type: LibraryType,
178 paths: Vec<PathBuf>,
179 ) -> Self {
180 let now = Utc::now();
181 Self {
182 id: LibraryId::new(),
183 name,
184 library_type,
185 paths,
186 scan_interval_minutes: 60,
187 last_scan: None,
188 enabled: true,
189 auto_scan: true,
190 watch_for_changes: true,
191 analyze_on_scan: false,
192 max_retry_attempts: 3,
193 movie_ref_batch_size: MovieReferenceBatchSize::default(),
194 created_at: now,
195 updated_at: now,
196 media: None,
197 }
198 }
199
200 fn update_last_scan(&mut self) {
201 self.last_scan = Some(Utc::now());
202 self.updated_at = Utc::now();
203 }
204
205 fn set_paths(&mut self, paths: Vec<PathBuf>) {
206 self.paths = paths;
207 self.updated_at = Utc::now();
208 }
209
210 fn set_scan_interval(&mut self, interval: u32) {
211 self.scan_interval_minutes = interval;
212 self.updated_at = Utc::now();
213 }
214
215 fn set_last_scan(&mut self, last_scan: Option<DateTime<Utc>>) {
216 self.last_scan = last_scan;
217 self.updated_at = Utc::now();
218 }
219
220 fn set_auto_scan(&mut self, auto_scan: bool) {
221 self.auto_scan = auto_scan;
222 self.updated_at = Utc::now();
223 }
224
225 fn set_max_retry_attempts(&mut self, max_retry_attempts: u32) {
226 self.max_retry_attempts = max_retry_attempts;
227 self.updated_at = Utc::now();
228 }
229
230 fn set_updated_at(&mut self, updated_at: Option<DateTime<Utc>>) {
231 self.updated_at = updated_at.unwrap_or_else(Utc::now);
232 }
233
234 fn set_media_references(&mut self, media_references: Vec<Media>) {
235 self.media = Some(media_references);
236 self.updated_at = Utc::now();
237 }
238}
239
240#[derive(Debug, Clone)]
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245pub struct LibraryScanResult {
246 pub library_id: LibraryId,
247 pub library_name: String,
248 pub total_files: usize,
249 pub new_files: usize,
250 pub updated_files: usize,
251 pub deleted_files: usize,
252 pub errors: Vec<String>,
253 pub duration_seconds: f64,
254}
255
256#[cfg(feature = "rkyv")]
257pub use archived::ArchivedLibraryExt;
258
259#[cfg(feature = "rkyv")]
260mod archived {
261 use super::*;
262 use crate::ids::ArchivedLibraryId;
263 use crate::media::{ArchivedMedia, ArchivedMovieReference};
264 use rkyv::{option::ArchivedOption, vec::ArchivedVec};
265
266 pub trait ArchivedLibraryExt {
267 fn media(&self) -> Option<&ArchivedVec<ArchivedMedia>>;
268 fn media_as_slice(&self) -> &[ArchivedMedia];
269 fn get_movie_refs(
270 &self,
271 ) -> impl Iterator<Item = &ArchivedMovieReference>;
272 }
273
274 impl ArchivedLibraryExt for ArchivedLibrary {
275 fn media(&self) -> Option<&ArchivedVec<ArchivedMedia>> {
276 match &self.media {
277 ArchivedOption::Some(media) => Some(media),
278 ArchivedOption::None => None,
279 }
280 }
281
282 fn media_as_slice(&self) -> &[ArchivedMedia] {
283 match &self.media {
284 ArchivedOption::Some(media) => media.as_slice(),
285 ArchivedOption::None => &[],
286 }
287 }
288
289 fn get_movie_refs(
290 &self,
291 ) -> impl Iterator<Item = &ArchivedMovieReference> {
292 self.media_as_slice().iter().filter_map(|m| match m {
293 ArchivedMedia::Movie(movie) => Some(movie.get()),
294 _ => None,
295 })
296 }
297 }
298
299 impl ArchivedLibrary {
300 pub fn get_id(&self) -> ArchivedLibraryId {
301 self.id
302 }
303
304 pub fn get_name(&self) -> &str {
305 &self.name
306 }
307
308 pub fn get_type(&self) -> &ArchivedLibraryType {
309 &self.library_type
310 }
311
312 pub fn is_enabled(&self) -> bool {
313 self.enabled
314 }
315
316 pub fn is_auto_scan(&self) -> bool {
317 self.auto_scan
318 }
319
320 pub fn is_watch_for_changes(&self) -> bool {
321 self.watch_for_changes
322 }
323
324 pub fn is_analyze_on_scan(&self) -> bool {
325 self.analyze_on_scan
326 }
327 }
328}