You've already forked flix
Update dependencies and lints
This commit is contained in:
+10
-5
@@ -1,14 +1,16 @@
|
||||
[package]
|
||||
name = "flix-fs"
|
||||
version = "0.0.20"
|
||||
license-file.workspace = true
|
||||
|
||||
version = "0.1.0"
|
||||
description = "Filesystem scanner for flix media"
|
||||
repository = "https://github.com/QuantumShade/flix"
|
||||
categories = []
|
||||
repository = "https://git.skrundz.dev/quantumshade/flix"
|
||||
keywords = ["flix"]
|
||||
categories = ["multimedia"]
|
||||
include = ["/src"]
|
||||
publish = true
|
||||
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license-file.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -23,5 +25,8 @@ thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true, features = ["fs"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+14
-10
@@ -1,39 +1,43 @@
|
||||
//! Filesystem errors.
|
||||
|
||||
use std::io;
|
||||
|
||||
/// The error type for filesystem scanning operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[expect(clippy::error_impl_error, reason = "Error is a good name here")]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to add more variants")]
|
||||
pub enum Error {
|
||||
/// fs::read_dir failed
|
||||
/// `fs::read_dir` failed.
|
||||
#[error("fs::read_dir: {0}")]
|
||||
ReadDir(io::Error),
|
||||
/// fs::read_dir::next_entry failed
|
||||
/// `fs::read_dir::next_entry` failed.
|
||||
#[error("fs::read_dir::next_entry: {0}")]
|
||||
ReadDirEntry(io::Error),
|
||||
/// fs::read_dir::file_type failed
|
||||
/// `fs::read_dir::file_type` failed.
|
||||
#[error("fs::read_dir::file_type: {0}")]
|
||||
FileType(io::Error),
|
||||
|
||||
/// There is an unexpected file in the directory
|
||||
/// There is an unexpected file in the directory.
|
||||
#[error("unexpected file")]
|
||||
UnexpectedFile,
|
||||
/// There is an unexpected folder in the directory
|
||||
/// There is an unexpected folder in the directory.
|
||||
#[error("unexpected folder")]
|
||||
UnexpectedFolder,
|
||||
/// There is an unexpected non-file item in the directory
|
||||
/// There is an unexpected non-file item in the directory.
|
||||
#[error("unexpected non-file")]
|
||||
UnexpectedNonFile,
|
||||
|
||||
/// There are multiple media files in the directory
|
||||
/// There are multiple media files in the directory.
|
||||
#[error("duplicate media file")]
|
||||
DuplicateMediaFile,
|
||||
/// There are multiple poster files in the directory
|
||||
/// There are multiple poster files in the directory.
|
||||
#[error("duplicate poster file")]
|
||||
DuplicatePosterFile,
|
||||
|
||||
/// The directory contains incomplete flix media
|
||||
/// The directory contains incomplete flix media.
|
||||
#[error("incomplete")]
|
||||
Incomplete,
|
||||
/// Some data is inconsistent with the folder structure
|
||||
/// Some data is inconsistent with the folder structure.
|
||||
#[error("inconsistent")]
|
||||
Inconsistent,
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
//! Filesystem scan result items.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// An item returned by scanner streams
|
||||
/// An item returned by scanner streams.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
|
||||
pub struct Item<T> {
|
||||
/// The path of the item
|
||||
/// The path of the item.
|
||||
pub path: PathBuf,
|
||||
/// The event relating to the item
|
||||
/// The event relating to the item.
|
||||
pub event: Result<T, Error>,
|
||||
}
|
||||
|
||||
impl<T> Item<T> {
|
||||
/// Helper function for mapping the inner event [Result]
|
||||
/// Helper function for mapping the inner event [Result].
|
||||
#[inline]
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Item<U> {
|
||||
Item {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! flix-fs provides filesystem scanners for flix media
|
||||
//! flix-fs provides filesystem scanners for flix media.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! Helper macros.
|
||||
|
||||
/// Defines media file extensions.
|
||||
macro_rules! is_media_extension {
|
||||
() => {
|
||||
Some("mp4" | "mkv")
|
||||
@@ -5,6 +8,7 @@ macro_rules! is_media_extension {
|
||||
}
|
||||
pub(super) use is_media_extension;
|
||||
|
||||
/// Defines image file expensions.
|
||||
macro_rules! is_image_extension {
|
||||
() => {
|
||||
Some("png" | "jpg")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The collection scanner will scan a folder and its children
|
||||
//! The collection scanner will scan a folder and its children.
|
||||
|
||||
use core::pin::Pin;
|
||||
use std::ffi::OsStr;
|
||||
@@ -17,24 +17,27 @@ use crate::scanner::{
|
||||
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, generic, movie, show,
|
||||
};
|
||||
|
||||
/// A collection item
|
||||
/// A collection item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for collections
|
||||
/// The scanner for collections.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned collection
|
||||
/// A scanned collection.
|
||||
Collection(CollectionScan),
|
||||
/// A scanned movie
|
||||
/// A scanned movie.
|
||||
Movie(MovieScan),
|
||||
/// A scanned show
|
||||
/// A scanned show.
|
||||
Show(ShowScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<movie::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: movie::Scanner) -> Self {
|
||||
match value {
|
||||
movie::Scanner::Movie(m) => Self::Movie(m),
|
||||
@@ -43,6 +46,7 @@ impl From<movie::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl From<show::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: show::Scanner) -> Self {
|
||||
match value {
|
||||
show::Scanner::Show(s) => Self::Show(s),
|
||||
@@ -53,6 +57,7 @@ impl From<show::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl From<generic::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: generic::Scanner) -> Self {
|
||||
match value {
|
||||
generic::Scanner::Collection(c) => Self::Collection(c),
|
||||
@@ -65,7 +70,12 @@ impl From<generic::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a collection
|
||||
/// Scan a folder for a collection.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_collection(
|
||||
path: &Path,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
@@ -151,7 +161,7 @@ impl Scanner {
|
||||
for await event in
|
||||
generic::Scanner::scan_detect_folder(&subdir, Some(id_ref.clone()))
|
||||
{
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The episode scanner will scan a folder and exit
|
||||
//! The episode scanner will scan a folder and exit.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
@@ -15,17 +15,23 @@ use crate::Error;
|
||||
use crate::macros::{is_image_extension, is_media_extension};
|
||||
use crate::scanner::{EpisodeScan, MediaRef};
|
||||
|
||||
/// An episode item
|
||||
/// An episode item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for epispdes
|
||||
/// The scanner for epispdes.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for an episode
|
||||
/// Scan a folder for an episode.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_episode(
|
||||
path: &Path,
|
||||
show_ref: MediaRef<ShowId>,
|
||||
@@ -62,6 +68,7 @@ impl Scanner {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
#[expect(clippy::filetype_is_file, reason = "we only want regular files")]
|
||||
if !filetype.is_file() {
|
||||
yield Item {
|
||||
path,
|
||||
|
||||
@@ -19,28 +19,32 @@ use crate::scanner::{
|
||||
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, collection, movie, show,
|
||||
};
|
||||
|
||||
/// Regex for detecting a media folder.
|
||||
static MEDIA_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
/// Regex for detecting a season folder.
|
||||
static SEASON_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
/// A collection item
|
||||
/// A collection item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for collections
|
||||
/// The scanner for collections.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned collection
|
||||
/// A scanned collection.
|
||||
Collection(CollectionScan),
|
||||
/// A scanned movie
|
||||
/// A scanned movie.
|
||||
Movie(MovieScan),
|
||||
/// A scanned show
|
||||
/// A scanned show.
|
||||
Show(ShowScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<collection::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: collection::Scanner) -> Self {
|
||||
match value {
|
||||
collection::Scanner::Collection(c) => Self::Collection(c),
|
||||
@@ -53,6 +57,7 @@ impl From<collection::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl From<movie::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: movie::Scanner) -> Self {
|
||||
match value {
|
||||
movie::Scanner::Movie(m) => Self::Movie(m),
|
||||
@@ -61,6 +66,7 @@ impl From<movie::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl From<show::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: show::Scanner) -> Self {
|
||||
match value {
|
||||
show::Scanner::Show(s) => Self::Show(s),
|
||||
@@ -71,13 +77,13 @@ impl From<show::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Helper function for stripping allowed numerical prefixes for sorting ("01 - ")
|
||||
fn strip_numeric_prefix(original: &str) -> &str {
|
||||
let mut s = original;
|
||||
while let Some('0'..='9') = s.chars().next() {
|
||||
s = &s[1..]
|
||||
}
|
||||
s.strip_prefix(" - ").unwrap_or(original)
|
||||
/// Helper function for stripping allowed numerical prefixes for sorting ("01 - ").
|
||||
#[inline]
|
||||
fn strip_numeric_prefix(string: &str) -> &str {
|
||||
string
|
||||
.trim_start_matches(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
|
||||
.strip_prefix(" - ")
|
||||
.unwrap_or(string)
|
||||
}
|
||||
|
||||
/// Detect the type of a folder and call the correct scanner. Use
|
||||
@@ -85,6 +91,13 @@ impl Scanner {
|
||||
/// - Collections
|
||||
/// - Movies
|
||||
/// - Shows
|
||||
///
|
||||
/// # Panics
|
||||
/// If a static regex is invalid.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_detect_folder(
|
||||
path: &Path,
|
||||
parent: Option<MediaRef<CollectionId>>,
|
||||
@@ -95,12 +108,14 @@ impl Scanner {
|
||||
Show,
|
||||
}
|
||||
|
||||
#[expect(clippy::panic, reason = "static regex string")]
|
||||
let media_folder_re = MEDIA_FOLDER_REGEX.get_or_init(|| {
|
||||
Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\)( \[[[:digit:]]+\])?$")
|
||||
.unwrap_or_else(|err| panic!("regex is invalid: {err}"))
|
||||
});
|
||||
#[expect(clippy::panic, reason = "static regex string")]
|
||||
let season_folder_re = SEASON_FOLDER_REGEX.get_or_init(|| {
|
||||
Regex::new(r"^S[[:digit:]]+$").unwrap_or_else(|err| panic!("regex is invalid: {err}"))
|
||||
Regex::new("^S[[:digit:]]+$").unwrap_or_else(|err| panic!("regex is invalid: {err}"))
|
||||
});
|
||||
|
||||
stream!({
|
||||
@@ -204,7 +219,7 @@ impl Scanner {
|
||||
};
|
||||
|
||||
for await event in collection::Scanner::scan_collection(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
MediaType::Movie => {
|
||||
@@ -214,7 +229,7 @@ impl Scanner {
|
||||
};
|
||||
|
||||
for await event in movie::Scanner::scan_movie(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
MediaType::Show => {
|
||||
@@ -224,7 +239,7 @@ impl Scanner {
|
||||
};
|
||||
|
||||
for await event in show::Scanner::scan_show(path, parent, id) {
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The library scanner will scan an entire directory using the generic
|
||||
//! scanner
|
||||
//! scanner.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
@@ -12,14 +12,20 @@ use tokio_stream::wrappers::ReadDirStream;
|
||||
use crate::Error;
|
||||
use crate::scanner::generic;
|
||||
|
||||
/// A library item
|
||||
/// A library item.
|
||||
pub type Item = crate::Item<generic::Scanner>;
|
||||
|
||||
/// The scanner for collections
|
||||
/// The scanner for collections.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a library
|
||||
/// Scan a folder for a library.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_library(path: &Path) -> impl Stream<Item = Item> {
|
||||
stream!({
|
||||
let dirs = match fs::read_dir(path).await {
|
||||
@@ -58,7 +64,7 @@ impl Scanner {
|
||||
match path.extension().and_then(OsStr::to_str) {
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: path.clone(),
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! This module contains all of the filesystem scanner modules
|
||||
//! This module contains all of the filesystem scanner modules.
|
||||
//!
|
||||
//! The most common scanner to use is [generic::Scanner] which will
|
||||
//! 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};
|
||||
@@ -18,82 +18,118 @@ pub mod episode;
|
||||
pub mod season;
|
||||
pub mod show;
|
||||
|
||||
/// A reference to a piece of media
|
||||
/// A reference to a piece of media.
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MediaRef<ID> {
|
||||
/// An explicit ID
|
||||
/// An explicit ID.
|
||||
Id(ID),
|
||||
/// A filesystem slug
|
||||
/// A filesystem slug.
|
||||
Slug(String),
|
||||
}
|
||||
|
||||
impl<ID> MediaRef<ID> {
|
||||
/// Get the slug if it exists
|
||||
/// Get the slug if it exists.
|
||||
#[inline]
|
||||
pub fn into_slug(self) -> Option<String> {
|
||||
match self {
|
||||
MediaRef::Id(_) => None,
|
||||
MediaRef::Slug(slug) => Some(slug),
|
||||
Self::Id(_) => None,
|
||||
Self::Slug(slug) => Some(slug),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A scanned collection
|
||||
/// A scanned collection.
|
||||
#[derive(Debug)]
|
||||
#[expect(
|
||||
clippy::exhaustive_structs,
|
||||
reason = "it will be a breaking change when more information is scanned"
|
||||
)]
|
||||
pub struct CollectionScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
/// The ID of the parent collection (if any).
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the collection
|
||||
/// The ID of the collection.
|
||||
pub id_ref: MediaRef<CollectionId>,
|
||||
/// The file name of the poster file
|
||||
/// The file name of the poster file.
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned movie
|
||||
/// A scanned movie.
|
||||
#[derive(Debug)]
|
||||
#[expect(
|
||||
clippy::exhaustive_structs,
|
||||
reason = "it will be a breaking change when more information is scanned"
|
||||
)]
|
||||
pub struct MovieScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
/// The ID of the parent collection (if any).
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the movie
|
||||
/// The ID of the movie.
|
||||
pub id_ref: MediaRef<MovieId>,
|
||||
/// The file name of the media file
|
||||
/// The file name of the media file.
|
||||
pub media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
/// The file name of the poster file.
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned show
|
||||
/// A scanned show.
|
||||
#[derive(Debug)]
|
||||
#[expect(
|
||||
clippy::exhaustive_structs,
|
||||
reason = "it will be a breaking change when more information is scanned"
|
||||
)]
|
||||
pub struct ShowScan {
|
||||
/// The ID of the parent collection (if any)
|
||||
/// The ID of the parent collection (if any).
|
||||
pub parent_ref: Option<MediaRef<CollectionId>>,
|
||||
/// The ID of the show
|
||||
/// The ID of the show.
|
||||
pub id_ref: MediaRef<ShowId>,
|
||||
/// The file name of the poster file
|
||||
/// The file name of the poster file.
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned season
|
||||
/// A scanned season.
|
||||
#[derive(Debug)]
|
||||
#[expect(
|
||||
clippy::exhaustive_structs,
|
||||
reason = "it will be a breaking change when more information is scanned"
|
||||
)]
|
||||
pub struct SeasonScan {
|
||||
/// The ID of the show this season belongs to
|
||||
/// The ID of the show this season belongs to.
|
||||
pub show_ref: MediaRef<ShowId>,
|
||||
/// The season this episode belongs to
|
||||
/// The season this episode belongs to.
|
||||
pub season: SeasonNumber,
|
||||
/// The file name of the poster file
|
||||
/// The file name of the poster file.
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
#[derive(Debug)]
|
||||
#[expect(
|
||||
clippy::exhaustive_structs,
|
||||
reason = "it will be a breaking change when more information is scanned"
|
||||
)]
|
||||
pub struct EpisodeScan {
|
||||
/// The ID of the show this episode belongs to
|
||||
/// The ID of the show this episode belongs to.
|
||||
pub show_ref: MediaRef<ShowId>,
|
||||
/// The season this episode belongs to
|
||||
/// The season this episode belongs to.
|
||||
pub season: SeasonNumber,
|
||||
/// The number(s) of this episode
|
||||
/// The number(s) of this episode.
|
||||
pub episode: EpisodeNumbers,
|
||||
/// The file name of the media file
|
||||
/// The file name of the media file.
|
||||
pub media_file_name: String,
|
||||
/// The file name of the poster file
|
||||
/// The file name of the poster file.
|
||||
pub poster_file_name: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{MediaRef, MovieId, library};
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[test]
|
||||
#[expect(clippy::missing_panics_doc, reason = "unit test")]
|
||||
fn use_fn() {
|
||||
let tmp = tempfile::tempdir().expect("temporary directory exists");
|
||||
drop(library::Scanner::scan_library(tmp.path()));
|
||||
drop(MediaRef::Id(MovieId::from_raw(0)).into_slug());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The movie scanner will scan a folder and exit
|
||||
//! The movie scanner will scan a folder and exit.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
@@ -14,17 +14,23 @@ use crate::Error;
|
||||
use crate::macros::{is_image_extension, is_media_extension};
|
||||
use crate::scanner::{MediaRef, MovieScan};
|
||||
|
||||
/// An movie item
|
||||
/// An movie item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for movies
|
||||
/// The scanner for movies.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned movie
|
||||
/// A scanned movie.
|
||||
Movie(MovieScan),
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a movie
|
||||
/// Scan a folder for a movie.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_movie(
|
||||
path: &Path,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
@@ -60,6 +66,7 @@ impl Scanner {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
#[expect(clippy::filetype_is_file, reason = "we only want regular files")]
|
||||
if !filetype.is_file() {
|
||||
yield Item {
|
||||
path,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! The episode scanner will scan a folder and its children
|
||||
//! The episode scanner will scan a folder and its children.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use flix_model::id::ShowId;
|
||||
use flix_model::numbers::{EpisodeNumber, EpisodeNumbers, SeasonNumber};
|
||||
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
|
||||
|
||||
use async_stream::stream;
|
||||
use tokio::fs;
|
||||
@@ -15,18 +15,21 @@ use crate::Error;
|
||||
use crate::macros::is_image_extension;
|
||||
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, episode};
|
||||
|
||||
/// A season item
|
||||
/// A season item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for seasons
|
||||
/// The scanner for seasons.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned season
|
||||
/// A scanned season.
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<episode::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: episode::Scanner) -> Self {
|
||||
match value {
|
||||
episode::Scanner::Episode(e) => Self::Episode(e),
|
||||
@@ -35,7 +38,11 @@ impl From<episode::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a season and its episodes
|
||||
/// Scan a folder for a season and its episodes.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_season(
|
||||
path: &Path,
|
||||
show_ref: MediaRef<ShowId>,
|
||||
@@ -158,7 +165,7 @@ impl Scanner {
|
||||
|
||||
let Ok(episode_numbers) = e_str
|
||||
.split('E')
|
||||
.map(|s| s.parse::<EpisodeNumber>())
|
||||
.map(str::parse)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
else {
|
||||
yield Item {
|
||||
@@ -181,7 +188,7 @@ impl Scanner {
|
||||
season_number,
|
||||
episode_numbers,
|
||||
) {
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The show scanner will scan a folder and its children
|
||||
//! The show scanner will scan a folder and its children.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
@@ -15,20 +15,23 @@ use crate::Error;
|
||||
use crate::macros::is_image_extension;
|
||||
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, ShowScan, season};
|
||||
|
||||
/// A show item
|
||||
/// A show item.
|
||||
pub type Item = crate::Item<Scanner>;
|
||||
|
||||
/// The scanner for shows
|
||||
/// The scanner for shows.
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "unlikely to change")]
|
||||
pub enum Scanner {
|
||||
/// A scanned show
|
||||
/// A scanned show.
|
||||
Show(ShowScan),
|
||||
/// A scanned season
|
||||
/// A scanned season.
|
||||
Season(SeasonScan),
|
||||
/// A scanned episode
|
||||
/// A scanned episode.
|
||||
Episode(EpisodeScan),
|
||||
}
|
||||
|
||||
impl From<season::Scanner> for Scanner {
|
||||
#[inline]
|
||||
fn from(value: season::Scanner) -> Self {
|
||||
match value {
|
||||
season::Scanner::Season(s) => Self::Season(s),
|
||||
@@ -38,7 +41,11 @@ impl From<season::Scanner> for Scanner {
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a folder for a show and its seasons/episodes
|
||||
/// Scan a folder for a show and its seasons/episodes.
|
||||
#[inline]
|
||||
#[expect(clippy::allow_attributes, reason = "for tail_expr_drop_order")]
|
||||
#[allow(tail_expr_drop_order, reason = "bug in stream! macro")]
|
||||
#[expect(clippy::semicolon_if_nothing_returned, reason = "false positive")]
|
||||
pub fn scan_show(
|
||||
path: &Path,
|
||||
parent_ref: Option<MediaRef<CollectionId>>,
|
||||
@@ -143,7 +150,7 @@ impl Scanner {
|
||||
for await event in
|
||||
season::Scanner::scan_season(&season_dir, id_ref.clone(), season_number)
|
||||
{
|
||||
yield event.map(|e| e.into());
|
||||
yield event.map(Into::into);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user