flix_fs/scanner/
episode.rs1use std::ffi::OsStr;
4use std::path::Path;
5
6use flix_model::id::ShowId;
7use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
8
9use async_stream::stream;
10use tokio::fs;
11use tokio_stream::Stream;
12use tokio_stream::wrappers::ReadDirStream;
13
14use crate::Error;
15use crate::macros::{is_image_extension, is_media_extension};
16use crate::scanner::{EpisodeScan, MediaRef};
17
18pub type Item = crate::Item<Scanner>;
20
21pub enum Scanner {
23 Episode(EpisodeScan),
25}
26
27impl Scanner {
28 pub fn scan_episode(
30 path: &Path,
31 show_ref: MediaRef<ShowId>,
32 season: SeasonNumber,
33 episode: EpisodeNumbers,
34 ) -> impl Stream<Item = Item> {
35 stream!({
36 let dirs = match fs::read_dir(path).await {
37 Ok(dirs) => dirs,
38 Err(err) => {
39 yield Item {
40 path: path.to_owned(),
41 event: Err(Error::ReadDir(err)),
42 };
43 return;
44 }
45 };
46
47 let mut media_file_name = None;
48 let mut poster_file_name = None;
49
50 for await dir in ReadDirStream::new(dirs) {
51 match dir {
52 Ok(dir) => {
53 let path = dir.path();
54
55 let filetype = match dir.file_type().await {
56 Ok(filetype) => filetype,
57 Err(err) => {
58 yield Item {
59 path,
60 event: Err(Error::FileType(err)),
61 };
62 continue;
63 }
64 };
65 if !filetype.is_file() {
66 yield Item {
67 path,
68 event: Err(Error::UnexpectedNonFile),
69 };
70 continue;
71 }
72
73 match path.extension().and_then(OsStr::to_str) {
74 is_media_extension!() => {
75 if media_file_name.is_some() {
76 yield Item {
77 path,
78 event: Err(Error::DuplicateMediaFile),
79 };
80 continue;
81 }
82 media_file_name = path
83 .file_name()
84 .and_then(|s| s.to_str())
85 .map(ToOwned::to_owned);
86 continue;
87 }
88 is_image_extension!() => {
89 if poster_file_name.is_some() {
90 yield Item {
91 path,
92 event: Err(Error::DuplicatePosterFile),
93 };
94 continue;
95 }
96 poster_file_name = path
97 .file_name()
98 .and_then(|s| s.to_str())
99 .map(ToOwned::to_owned);
100 }
101 Some(_) | None => {
102 yield Item {
103 path,
104 event: Err(Error::UnexpectedFile),
105 };
106 }
107 }
108 }
109 Err(err) => {
110 yield Item {
111 path: path.to_owned(),
112 event: Err(Error::ReadDirEntry(err)),
113 }
114 }
115 }
116 }
117
118 let Some(media_file_name) = media_file_name else {
119 yield Item {
120 path: path.to_owned(),
121 event: Err(Error::Incomplete),
122 };
123 return;
124 };
125
126 yield Item {
127 path: path.to_owned(),
128 event: Ok(Self::Episode(EpisodeScan {
129 show_ref,
130 season,
131 episode,
132 media_file_name,
133 poster_file_name,
134 })),
135 };
136 })
137 }
138}