You've already forked flix
Update all libraries to the new database format
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flix-fs"
|
||||
version = "0.0.16"
|
||||
version = "0.0.17"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Filesystem scanner for flix media"
|
||||
@@ -14,6 +14,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
async-stream = { workspace = true }
|
||||
either = { workspace = true }
|
||||
flix-model = { workspace = true }
|
||||
regex = { workspace = true, features = ["perf", "std"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -4,8 +4,7 @@ use core::pin::Pin;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use flix_model::id::{CollectionId, MovieId, ShowId};
|
||||
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
|
||||
use flix_model::id::CollectionId;
|
||||
|
||||
use async_stream::stream;
|
||||
use tokio::fs;
|
||||
@@ -14,7 +13,9 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::macros::is_image_extension;
|
||||
use crate::scanner::{generic, movie, show};
|
||||
use crate::scanner::{
|
||||
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, generic, movie, show,
|
||||
};
|
||||
|
||||
/// A collection item
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
@@ -22,74 +23,21 @@ pub type Item = crate::Item<Scanner>;
|
||||
/// The scanner for collections
|
||||
pub enum Scanner {
|
||||
/// A scanned collection
|
||||
Collection {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the collection
|
||||
id: CollectionId,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
|
||||
Collection(CollectionScan),
|
||||
/// A scanned movie
|
||||
Movie {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the movie
|
||||
id: MovieId,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
|
||||
Movie(MovieScan),
|
||||
/// A scanned show
|
||||
Show {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the show
|
||||
id: ShowId,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Show(ShowScan),
|
||||
/// A scanned episode
|
||||
Season {
|
||||
/// The ID of the show this season belongs to
|
||||
show: ShowId,
|
||||
/// The number of this season
|
||||
season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
Episode {
|
||||
/// The ID of the show this episode belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<movie::Scanner> for Scanner {
|
||||
fn from(value: movie::Scanner) -> Self {
|
||||
match value {
|
||||
movie::Scanner::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
movie::Scanner::Movie(m) => Self::Movie(m),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,37 +45,9 @@ impl From<movie::Scanner> for Scanner {
|
||||
impl From<show::Scanner> for Scanner {
|
||||
fn from(value: show::Scanner) -> Self {
|
||||
match value {
|
||||
show::Scanner::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
} => Self::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Show(s) => Self::Show(s),
|
||||
show::Scanner::Season(s) => Self::Season(s),
|
||||
show::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,57 +55,11 @@ impl From<show::Scanner> for Scanner {
|
||||
impl From<generic::Scanner> for Scanner {
|
||||
fn from(value: generic::Scanner) -> Self {
|
||||
match value {
|
||||
generic::Scanner::Collection {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Collection {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
generic::Scanner::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
generic::Scanner::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
generic::Scanner::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
} => Self::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
},
|
||||
generic::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
generic::Scanner::Collection(c) => Self::Collection(c),
|
||||
generic::Scanner::Movie(m) => Self::Movie(m),
|
||||
generic::Scanner::Show(s) => Self::Show(s),
|
||||
generic::Scanner::Season(s) => Self::Season(s),
|
||||
generic::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,8 +68,8 @@ impl Scanner {
|
||||
/// Scan a folder for a collection
|
||||
pub fn scan_collection(
|
||||
path: &Path,
|
||||
parent: Option<CollectionId>,
|
||||
id: CollectionId,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
id_ref: MediaRef<CollectionId>,
|
||||
) -> Pin<Box<impl Stream<Item = Item>>> {
|
||||
Box::pin(stream!({
|
||||
let dirs = match fs::read_dir(path).await {
|
||||
@@ -266,15 +140,17 @@ impl Scanner {
|
||||
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Ok(Self::Collection {
|
||||
parent,
|
||||
id,
|
||||
event: Ok(Self::Collection(CollectionScan {
|
||||
parent_ref,
|
||||
id_ref: id_ref.clone(),
|
||||
poster_file_name,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
|
||||
for subdir in subdirs_to_scan {
|
||||
for await event in generic::Scanner::scan_detect_folder(&subdir, Some(id)) {
|
||||
for await event in
|
||||
generic::Scanner::scan_detect_folder(&subdir, Some(id_ref.clone()))
|
||||
{
|
||||
yield event.map(|e| e.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::macros::{is_image_extension, is_media_extension};
|
||||
use crate::scanner::{EpisodeScan, MediaRef};
|
||||
|
||||
/// An episode item
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
@@ -20,25 +21,14 @@ pub type Item = crate::Item<Scanner>;
|
||||
/// The scanner for epispdes
|
||||
pub enum Scanner {
|
||||
/// A scanned episode
|
||||
Episode {
|
||||
/// The ID of the show this episode belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for an episode
|
||||
pub fn scan_episode(
|
||||
path: &Path,
|
||||
show: ShowId,
|
||||
show_ref: MediaRef<ShowId>,
|
||||
season: SeasonNumber,
|
||||
episode: EpisodeNumbers,
|
||||
) -> impl Stream<Item = Item> {
|
||||
@@ -135,13 +125,13 @@ impl Scanner {
|
||||
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Ok(Self::Episode {
|
||||
show,
|
||||
event: Ok(Self::Episode(EpisodeScan {
|
||||
show_ref,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,16 +6,18 @@ use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use flix_model::id::{CollectionId, MovieId, RawId, ShowId};
|
||||
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
|
||||
|
||||
use async_stream::stream;
|
||||
use either::Either;
|
||||
use regex::Regex;
|
||||
use tokio::fs;
|
||||
use tokio_stream::Stream;
|
||||
use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::scanner::{collection, movie, show};
|
||||
use crate::scanner::{
|
||||
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, collection, movie, show,
|
||||
};
|
||||
|
||||
static MEDIA_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
static SEASON_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
@@ -24,116 +26,28 @@ static SEASON_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for collections
|
||||
#[derive(Debug)]
|
||||
pub enum Scanner {
|
||||
/// A scanned collection
|
||||
Collection {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the collection
|
||||
id: CollectionId,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
|
||||
Collection(CollectionScan),
|
||||
/// A scanned movie
|
||||
Movie {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the movie
|
||||
id: MovieId,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
|
||||
Movie(MovieScan),
|
||||
/// A scanned show
|
||||
Show {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the show
|
||||
id: ShowId,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Show(ShowScan),
|
||||
/// A scanned episode
|
||||
Season {
|
||||
/// The ID of the show this season belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
Episode {
|
||||
/// The ID of the show this episode belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<collection::Scanner> for Scanner {
|
||||
fn from(value: collection::Scanner) -> Self {
|
||||
match value {
|
||||
collection::Scanner::Collection {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Collection {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
collection::Scanner::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
collection::Scanner::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
collection::Scanner::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
} => Self::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
},
|
||||
collection::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
collection::Scanner::Collection(c) => Self::Collection(c),
|
||||
collection::Scanner::Movie(m) => Self::Movie(m),
|
||||
collection::Scanner::Show(s) => Self::Show(s),
|
||||
collection::Scanner::Season(s) => Self::Season(s),
|
||||
collection::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,17 +55,7 @@ impl From<collection::Scanner> for Scanner {
|
||||
impl From<movie::Scanner> for Scanner {
|
||||
fn from(value: movie::Scanner) -> Self {
|
||||
match value {
|
||||
movie::Scanner::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Movie {
|
||||
parent,
|
||||
id,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
movie::Scanner::Movie(m) => Self::Movie(m),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,42 +63,22 @@ impl From<movie::Scanner> for Scanner {
|
||||
impl From<show::Scanner> for Scanner {
|
||||
fn from(value: show::Scanner) -> Self {
|
||||
match value {
|
||||
show::Scanner::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
} => Self::Show {
|
||||
parent,
|
||||
id,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
} => Self::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
show::Scanner::Show(s) => Self::Show(s),
|
||||
show::Scanner::Season(s) => Self::Season(s),
|
||||
show::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Helper function for stripping allowed numerical prefixes for sorting ("01 - ")
|
||||
fn strip_numeric_prefix(mut s: &str) -> &str {
|
||||
while let Some('0'..='9') = s.chars().next() {
|
||||
s = &s[1..]
|
||||
}
|
||||
s.strip_prefix(" - ").unwrap_or(s)
|
||||
}
|
||||
|
||||
/// Detect the type of a folder and call the correct scanner. Use
|
||||
/// this only for detecting possibly ambiguous media:
|
||||
/// - Collections
|
||||
@@ -202,7 +86,7 @@ impl Scanner {
|
||||
/// - Shows
|
||||
pub fn scan_detect_folder(
|
||||
path: &Path,
|
||||
parent: Option<CollectionId>,
|
||||
parent: Option<MediaRef<CollectionId>>,
|
||||
) -> impl Stream<Item = Item> {
|
||||
enum MediaType {
|
||||
Collection,
|
||||
@@ -211,7 +95,7 @@ impl Scanner {
|
||||
}
|
||||
|
||||
let media_folder_re = MEDIA_FOLDER_REGEX.get_or_init(|| {
|
||||
Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\) \[[[:digit:]]+\]$")
|
||||
Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\)( \[[[:digit:]]+\])?$")
|
||||
.unwrap_or_else(|err| panic!("regex is invalid: {err}"))
|
||||
});
|
||||
let season_folder_re = SEASON_FOLDER_REGEX.get_or_init(|| {
|
||||
@@ -227,16 +111,23 @@ impl Scanner {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(Ok(id)) = dir_name
|
||||
let dir_name = Self::strip_numeric_prefix(dir_name);
|
||||
|
||||
// Use the explicit ID ("[X]") if it exists, otherwise parse the folder name
|
||||
let media_id = if let Some((id_str, _)) = dir_name
|
||||
.split_once('[')
|
||||
.and_then(|(_, s)| s.split_once(']'))
|
||||
.map(|(s, _)| s.parse::<RawId>())
|
||||
else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
{
|
||||
let Ok(id) = id_str.parse::<RawId>() else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
return;
|
||||
};
|
||||
return;
|
||||
Either::Left(id)
|
||||
} else {
|
||||
Either::Right(flix_model::text::normalize_fs_name(dir_name))
|
||||
};
|
||||
|
||||
let media_type: MediaType;
|
||||
@@ -306,24 +197,32 @@ impl Scanner {
|
||||
|
||||
match media_type {
|
||||
MediaType::Collection => {
|
||||
for await event in collection::Scanner::scan_collection(
|
||||
path,
|
||||
parent,
|
||||
CollectionId::from_raw(id),
|
||||
) {
|
||||
let id = match media_id {
|
||||
Either::Left(raw) => MediaRef::Id(CollectionId::from_raw(raw)),
|
||||
Either::Right(slug) => MediaRef::Slug(slug),
|
||||
};
|
||||
|
||||
for await event in collection::Scanner::scan_collection(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
}
|
||||
}
|
||||
MediaType::Movie => {
|
||||
for await event in
|
||||
movie::Scanner::scan_movie(path, parent, MovieId::from_raw(id))
|
||||
{
|
||||
let id = match media_id {
|
||||
Either::Left(raw) => MediaRef::Id(MovieId::from_raw(raw)),
|
||||
Either::Right(slug) => MediaRef::Slug(slug),
|
||||
};
|
||||
|
||||
for await event in movie::Scanner::scan_movie(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
}
|
||||
}
|
||||
MediaType::Show => {
|
||||
for await event in show::Scanner::scan_show(path, parent, ShowId::from_raw(id))
|
||||
{
|
||||
let id = match media_id {
|
||||
Either::Left(raw) => MediaRef::Id(ShowId::from_raw(raw)),
|
||||
Either::Right(slug) => MediaRef::Slug(slug),
|
||||
};
|
||||
|
||||
for await event in show::Scanner::scan_show(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
//! The most common scanner to use is [generic::Scanner] which will
|
||||
//! automatically detect and use the appropriate scanner.
|
||||
|
||||
use flix_model::id::{CollectionId, MovieId, ShowId};
|
||||
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
|
||||
|
||||
pub mod library;
|
||||
|
||||
pub mod generic;
|
||||
@@ -14,3 +17,73 @@ pub mod movie;
|
||||
pub mod episode;
|
||||
pub mod season;
|
||||
pub mod show;
|
||||
|
||||
/// A reference to a piece of media
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MediaRef<ID> {
|
||||
/// An explicit ID
|
||||
Id(ID),
|
||||
/// A filesystem slug
|
||||
Slug(String),
|
||||
}
|
||||
|
||||
/// A scanned collection
|
||||
#[derive(Debug)]
|
||||
pub struct CollectionScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the collection
|
||||
pub id_ref: MediaRef<CollectionId>,
|
||||
/// The file name of the poster file
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned movie
|
||||
#[derive(Debug)]
|
||||
pub struct MovieScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the movie
|
||||
pub id_ref: MediaRef<MovieId>,
|
||||
/// The file name of the media file
|
||||
pub media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned show
|
||||
#[derive(Debug)]
|
||||
pub struct ShowScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the show
|
||||
pub id_ref: MediaRef<ShowId>,
|
||||
/// The file name of the poster file
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned season
|
||||
#[derive(Debug)]
|
||||
pub struct SeasonScan {
|
||||
/// The ID of the show this season belongs to
|
||||
pub show_ref: MediaRef<ShowId>,
|
||||
/// The season this episode belongs to
|
||||
pub season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned episode
|
||||
#[derive(Debug)]
|
||||
pub struct EpisodeScan {
|
||||
/// The ID of the show this episode belongs to
|
||||
pub show_ref: MediaRef<ShowId>,
|
||||
/// The season this episode belongs to
|
||||
pub season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
pub episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
pub media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::macros::{is_image_extension, is_media_extension};
|
||||
use crate::scanner::{MediaRef, MovieScan};
|
||||
|
||||
/// An movie item
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
@@ -19,24 +20,15 @@ pub type Item = crate::Item<Scanner>;
|
||||
/// The scanner for movies
|
||||
pub enum Scanner {
|
||||
/// A scanned movie
|
||||
Movie {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the movie
|
||||
id: MovieId,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Movie(MovieScan),
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a movie
|
||||
pub fn scan_movie(
|
||||
path: &Path,
|
||||
parent: Option<CollectionId>,
|
||||
id: MovieId,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
id_ref: MediaRef<MovieId>,
|
||||
) -> impl Stream<Item = Item> {
|
||||
stream!({
|
||||
let dirs = match fs::read_dir(path).await {
|
||||
@@ -131,12 +123,12 @@ impl Scanner {
|
||||
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Ok(Self::Movie {
|
||||
parent,
|
||||
id,
|
||||
event: Ok(Self::Movie(MovieScan {
|
||||
parent_ref,
|
||||
id_ref,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,53 +13,23 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::macros::is_image_extension;
|
||||
use crate::scanner::episode;
|
||||
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, episode};
|
||||
|
||||
/// A season item
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for seasons
|
||||
pub enum Scanner {
|
||||
/// A scanned season
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
Season {
|
||||
/// The ID of the show this season belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
/// A scanned episode
|
||||
Episode {
|
||||
/// The ID of the show this episode belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<episode::Scanner> for Scanner {
|
||||
fn from(value: episode::Scanner) -> Self {
|
||||
match value {
|
||||
episode::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
episode::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +38,7 @@ impl Scanner {
|
||||
/// Scan a folder for a season and its episodes
|
||||
pub fn scan_season(
|
||||
path: &Path,
|
||||
show: ShowId,
|
||||
show_ref: MediaRef<ShowId>,
|
||||
season: SeasonNumber,
|
||||
) -> impl Stream<Item = Item> {
|
||||
stream!({
|
||||
@@ -140,11 +110,11 @@ impl Scanner {
|
||||
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Ok(Self::Season {
|
||||
show,
|
||||
event: Ok(Self::Season(SeasonScan {
|
||||
show_ref: show_ref.clone(),
|
||||
season,
|
||||
poster_file_name,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
|
||||
for episode_dir in episode_dirs_to_scan {
|
||||
@@ -207,7 +177,7 @@ impl Scanner {
|
||||
|
||||
for await event in episode::Scanner::scan_episode(
|
||||
&episode_dir,
|
||||
show,
|
||||
show_ref.clone(),
|
||||
season_number,
|
||||
episode_numbers,
|
||||
) {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use flix_model::id::{CollectionId, ShowId};
|
||||
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
|
||||
use flix_model::numbers::SeasonNumber;
|
||||
|
||||
use async_stream::stream;
|
||||
use tokio::fs;
|
||||
@@ -13,7 +13,7 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
|
||||
use crate::Error;
|
||||
use crate::macros::is_image_extension;
|
||||
use crate::scanner::season;
|
||||
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, ShowScan, season};
|
||||
|
||||
/// A show item
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
@@ -21,63 +21,18 @@ pub type Item = crate::Item<Scanner>;
|
||||
/// The scanner for shows
|
||||
pub enum Scanner {
|
||||
/// A scanned show
|
||||
Show {
|
||||
/// The ID of the parent collection (if any)
|
||||
parent: Option<CollectionId>,
|
||||
/// The ID of the show
|
||||
id: ShowId,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Show(ShowScan),
|
||||
/// A scanned season
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
Season {
|
||||
/// The ID of the show this season belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
/// A scanned episode
|
||||
Episode {
|
||||
/// The ID of the show this episode belongs to
|
||||
show: ShowId,
|
||||
/// The season this episode belongs to
|
||||
season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
poster_file_name: Option<String>,
|
||||
},
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<season::Scanner> for Scanner {
|
||||
fn from(value: season::Scanner) -> Self {
|
||||
match value {
|
||||
season::Scanner::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
} => Self::Season {
|
||||
show,
|
||||
season,
|
||||
poster_file_name,
|
||||
},
|
||||
season::Scanner::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
} => Self::Episode {
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
media_file_name,
|
||||
poster_file_name,
|
||||
},
|
||||
season::Scanner::Season(s) => Self::Season(s),
|
||||
season::Scanner::Episode(e) => Self::Episode(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,8 +41,8 @@ impl Scanner {
|
||||
/// Scan a folder for a show and its seasons/episodes
|
||||
pub fn scan_show(
|
||||
path: &Path,
|
||||
parent: Option<CollectionId>,
|
||||
id: ShowId,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
id_ref: MediaRef<ShowId>,
|
||||
) -> impl Stream<Item = Item> {
|
||||
stream!({
|
||||
let dirs = match fs::read_dir(path).await {
|
||||
@@ -158,11 +113,11 @@ impl Scanner {
|
||||
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
event: Ok(Self::Show {
|
||||
parent,
|
||||
id,
|
||||
event: Ok(Self::Show(ShowScan {
|
||||
parent_ref,
|
||||
id_ref: id_ref.clone(),
|
||||
poster_file_name,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
|
||||
for season_dir in season_dirs_to_scan {
|
||||
@@ -185,7 +140,9 @@ impl Scanner {
|
||||
continue;
|
||||
};
|
||||
|
||||
for await event in season::Scanner::scan_season(&season_dir, id, season_number) {
|
||||
for await event in
|
||||
season::Scanner::scan_season(&season_dir, id_ref.clone(), season_number)
|
||||
{
|
||||
yield event.map(|e| e.into());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user