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};
16
17pub type Item = crate::Item<Scanner>;
19
20pub enum Scanner {
22 Episode {
24 show: ShowId,
26 season: SeasonNumber,
28 episode: EpisodeNumbers,
30 media_file_name: String,
32 poster_file_name: Option<String>,
34 },
35}
36
37impl Scanner {
38 pub fn scan_episode(
40 path: &Path,
41 show: ShowId,
42 season: SeasonNumber,
43 episode: EpisodeNumbers,
44 ) -> impl Stream<Item = Item> {
45 stream!({
46 let dirs = match fs::read_dir(path).await {
47 Ok(dirs) => dirs,
48 Err(err) => {
49 yield Item {
50 path: path.to_owned(),
51 event: Err(Error::ReadDir(err)),
52 };
53 return;
54 }
55 };
56
57 let mut media_file_name = None;
58 let mut poster_file_name = None;
59
60 for await dir in ReadDirStream::new(dirs) {
61 match dir {
62 Ok(dir) => {
63 let filetype = match dir.file_type().await {
64 Ok(filetype) => filetype,
65 Err(err) => {
66 yield Item {
67 path: path.to_owned(),
68 event: Err(Error::FileType(err)),
69 };
70 continue;
71 }
72 };
73 if !filetype.is_file() {
74 yield Item {
75 path: path.to_owned(),
76 event: Err(Error::UnexpectedNonFile),
77 };
78 continue;
79 }
80
81 let path = dir.path();
82 match path.extension().and_then(OsStr::to_str) {
83 is_media_extension!() => {
84 if media_file_name.is_some() {
85 yield Item {
86 path: path.to_owned(),
87 event: Err(Error::DuplicateMediaFile),
88 };
89 continue;
90 }
91 media_file_name = path
92 .file_name()
93 .and_then(|s| s.to_str())
94 .map(ToOwned::to_owned);
95 continue;
96 }
97 is_image_extension!() => {
98 if poster_file_name.is_some() {
99 yield Item {
100 path: path.to_owned(),
101 event: Err(Error::DuplicatePosterFile),
102 };
103 continue;
104 }
105 poster_file_name = path
106 .file_name()
107 .and_then(|s| s.to_str())
108 .map(ToOwned::to_owned);
109 }
110 Some(_) | None => {
111 yield Item {
112 path: path.to_owned(),
113 event: Err(Error::UnexpectedFile),
114 };
115 }
116 }
117 }
118 Err(err) => {
119 yield Item {
120 path: path.to_owned(),
121 event: Err(Error::ReadDirEntry(err)),
122 }
123 }
124 }
125 }
126
127 let Some(media_file_name) = media_file_name else {
128 yield Item {
129 path: path.to_owned(),
130 event: Err(Error::Incomplete),
131 };
132 return;
133 };
134
135 yield Item {
136 path: path.to_owned(),
137 event: Ok(Self::Episode {
138 show,
139 season,
140 episode,
141 media_file_name,
142 poster_file_name,
143 }),
144 };
145 })
146 }
147}