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 path = dir.path();
64
65 let filetype = match dir.file_type().await {
66 Ok(filetype) => filetype,
67 Err(err) => {
68 yield Item {
69 path,
70 event: Err(Error::FileType(err)),
71 };
72 continue;
73 }
74 };
75 if !filetype.is_file() {
76 yield Item {
77 path,
78 event: Err(Error::UnexpectedNonFile),
79 };
80 continue;
81 }
82
83 match path.extension().and_then(OsStr::to_str) {
84 is_media_extension!() => {
85 if media_file_name.is_some() {
86 yield Item {
87 path,
88 event: Err(Error::DuplicateMediaFile),
89 };
90 continue;
91 }
92 media_file_name = path
93 .file_name()
94 .and_then(|s| s.to_str())
95 .map(ToOwned::to_owned);
96 continue;
97 }
98 is_image_extension!() => {
99 if poster_file_name.is_some() {
100 yield Item {
101 path,
102 event: Err(Error::DuplicatePosterFile),
103 };
104 continue;
105 }
106 poster_file_name = path
107 .file_name()
108 .and_then(|s| s.to_str())
109 .map(ToOwned::to_owned);
110 }
111 Some(_) | None => {
112 yield Item {
113 path,
114 event: Err(Error::UnexpectedFile),
115 };
116 }
117 }
118 }
119 Err(err) => {
120 yield Item {
121 path: path.to_owned(),
122 event: Err(Error::ReadDirEntry(err)),
123 }
124 }
125 }
126 }
127
128 let Some(media_file_name) = media_file_name else {
129 yield Item {
130 path: path.to_owned(),
131 event: Err(Error::Incomplete),
132 };
133 return;
134 };
135
136 yield Item {
137 path: path.to_owned(),
138 event: Ok(Self::Episode {
139 show,
140 season,
141 episode,
142 media_file_name,
143 poster_file_name,
144 }),
145 };
146 })
147 }
148}