You've already forked flix
Support flix collections
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
use clap::Subcommand;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum AddCommand {
|
||||
/// Process a flix collection
|
||||
Collection {
|
||||
#[arg(value_name = "TITLE")]
|
||||
title: String,
|
||||
#[arg(value_name = "OVERVIEW")]
|
||||
overview: String,
|
||||
},
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||
use anyhow::{Result, anyhow};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
pub mod flix;
|
||||
pub mod tmdb;
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -57,17 +58,17 @@ pub enum Command {
|
||||
/// Add new items to the database
|
||||
Add {
|
||||
#[command(subcommand)]
|
||||
command: BackendCommand,
|
||||
command: AddCommand,
|
||||
},
|
||||
/// Update an existing item in the database
|
||||
Update {
|
||||
#[command(subcommand)]
|
||||
command: BackendCommand,
|
||||
command: UpdateCommand,
|
||||
},
|
||||
/// Delete an existing item in the database
|
||||
Delete {
|
||||
#[command(subcommand)]
|
||||
command: BackendCommand,
|
||||
command: DeleteCommand,
|
||||
},
|
||||
/// Create a toml backup of the database
|
||||
Backup {
|
||||
@@ -84,7 +85,12 @@ pub enum Command {
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum BackendCommand {
|
||||
pub enum AddCommand {
|
||||
/// Use the flix backend
|
||||
Flix {
|
||||
#[command(subcommand)]
|
||||
command: flix::AddCommand,
|
||||
},
|
||||
/// Use the TMDB backend
|
||||
Tmdb {
|
||||
#[command(subcommand)]
|
||||
@@ -92,7 +98,43 @@ pub enum BackendCommand {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<tmdb::Command> for BackendCommand {
|
||||
impl From<flix::AddCommand> for AddCommand {
|
||||
fn from(value: flix::AddCommand) -> Self {
|
||||
Self::Flix { command: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tmdb::Command> for AddCommand {
|
||||
fn from(value: tmdb::Command) -> Self {
|
||||
Self::Tmdb { command: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum UpdateCommand {
|
||||
/// Use the TMDB backend
|
||||
Tmdb {
|
||||
#[command(subcommand)]
|
||||
command: tmdb::Command,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<tmdb::Command> for UpdateCommand {
|
||||
fn from(value: tmdb::Command) -> Self {
|
||||
Self::Tmdb { command: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum DeleteCommand {
|
||||
/// Use the TMDB backend
|
||||
Tmdb {
|
||||
#[command(subcommand)]
|
||||
command: tmdb::Command,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<tmdb::Command> for DeleteCommand {
|
||||
fn from(value: tmdb::Command) -> Self {
|
||||
Self::Tmdb { command: value }
|
||||
}
|
||||
|
||||
+13
-15
@@ -7,7 +7,7 @@ use clap::Parser;
|
||||
use tokio::fs;
|
||||
|
||||
mod cli;
|
||||
use cli::{BackendCommand, Cli, Command};
|
||||
use cli::{AddCommand, Cli, Command, DeleteCommand, UpdateCommand};
|
||||
|
||||
mod config;
|
||||
use config::Config;
|
||||
@@ -53,11 +53,14 @@ async fn exec_init(database_path: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_add(client: Client, database_path: String, command: BackendCommand) -> Result<()> {
|
||||
async fn exec_add(client: Client, database_path: String, command: AddCommand) -> Result<()> {
|
||||
let database = db::open(database_path).await?;
|
||||
|
||||
match command {
|
||||
BackendCommand::Tmdb { command } => {
|
||||
AddCommand::Flix { command } => {
|
||||
run::flix::add(database.as_ref(), command).await?;
|
||||
}
|
||||
AddCommand::Tmdb { command } => {
|
||||
run::tmdb::add(client, database.as_ref(), command).await?;
|
||||
}
|
||||
}
|
||||
@@ -65,11 +68,11 @@ async fn exec_add(client: Client, database_path: String, command: BackendCommand
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_update(client: Client, database_path: String, command: BackendCommand) -> Result<()> {
|
||||
async fn exec_update(client: Client, database_path: String, command: UpdateCommand) -> Result<()> {
|
||||
let database = db::open(database_path).await?;
|
||||
|
||||
match command {
|
||||
BackendCommand::Tmdb { command } => {
|
||||
UpdateCommand::Tmdb { command } => {
|
||||
run::tmdb::update(client, database.as_ref(), command).await?;
|
||||
}
|
||||
}
|
||||
@@ -77,16 +80,11 @@ async fn exec_update(client: Client, database_path: String, command: BackendComm
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_delete(client: Client, database_path: String, command: BackendCommand) -> Result<()> {
|
||||
let database = db::open(database_path).await?;
|
||||
|
||||
match command {
|
||||
BackendCommand::Tmdb { command } => {
|
||||
run::tmdb::delete(client, database.as_ref(), command).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
async fn exec_delete(client: Client, database_path: String, command: DeleteCommand) -> Result<()> {
|
||||
_ = client;
|
||||
_ = database_path;
|
||||
_ = command;
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn exec_backup(database_path: String, output: PathBuf) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use flix::db::entity;
|
||||
use flix::model::id::CollectionId;
|
||||
|
||||
use anyhow::Result;
|
||||
use sea_orm::ActiveValue::{NotSet, Set};
|
||||
use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, TransactionError, TransactionTrait};
|
||||
|
||||
use crate::cli::flix::AddCommand;
|
||||
|
||||
pub async fn add(db: &DatabaseConnection, command: AddCommand) -> Result<()> {
|
||||
match command {
|
||||
AddCommand::Collection { title, overview } => {
|
||||
let result: Result<CollectionId, TransactionError<DbErr>> = db
|
||||
.transaction(|txn| {
|
||||
let title = title.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let flix = entity::info::collections::ActiveModel {
|
||||
id: NotSet,
|
||||
title: Set(title),
|
||||
overview: Set(overview),
|
||||
}
|
||||
.insert(txn)
|
||||
.await?;
|
||||
|
||||
Ok(flix.id)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
let flix_id = match result {
|
||||
Ok(id) => id,
|
||||
Err(TransactionError::Connection(err)) => Err(err)?,
|
||||
Err(TransactionError::Transaction(err)) => Err(err)?,
|
||||
};
|
||||
println!("Created Collection: {} [{}]", title, flix_id.into_raw());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod flix;
|
||||
pub mod tmdb;
|
||||
|
||||
+22
-11
@@ -9,7 +9,7 @@ use flix::tmdb::model::id::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use chrono::Utc;
|
||||
use chrono::{Datelike, Utc};
|
||||
use sea_orm::ActiveValue::{NotSet, Set};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, TransactionError, TransactionTrait,
|
||||
@@ -35,6 +35,8 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
.await
|
||||
.with_context(|| format!("collections().get_details({})", id.into_raw()))?;
|
||||
|
||||
let title = collection.title.clone();
|
||||
|
||||
let result: Result<CollectionId, TransactionError<DbErr>> = db
|
||||
.transaction(|txn| {
|
||||
Box::pin(async move {
|
||||
@@ -65,7 +67,7 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
Err(TransactionError::Connection(err)) => Err(err)?,
|
||||
Err(TransactionError::Transaction(err)) => Err(err)?,
|
||||
};
|
||||
println!("Created Collection: {}", flix_id.into_raw());
|
||||
println!("Created Collection: {} [{}]", title, flix_id.into_raw());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -83,6 +85,9 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
.await
|
||||
.with_context(|| format!("movies().get_details({})", id.into_raw()))?;
|
||||
|
||||
let title = movie.title.clone();
|
||||
let year = movie.release_date.year();
|
||||
|
||||
let result: Result<MovieId, TransactionError<DbErr>> = db
|
||||
.transaction(|txn| {
|
||||
Box::pin(async move {
|
||||
@@ -116,7 +121,12 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
Err(TransactionError::Connection(err)) => Err(err)?,
|
||||
Err(TransactionError::Transaction(err)) => Err(err)?,
|
||||
};
|
||||
println!("Created Movie: {}", flix_id.into_raw());
|
||||
println!(
|
||||
"Created Movie: {} ({}) [{}]",
|
||||
title,
|
||||
year,
|
||||
flix_id.into_raw(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -136,6 +146,9 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
let mut seasons = Vec::new();
|
||||
let mut episodes = HashMap::new();
|
||||
|
||||
let title = show.title.clone();
|
||||
let year = show.first_air_date.year();
|
||||
|
||||
for season in 1..=show.number_of_seasons {
|
||||
let season = client
|
||||
.seasons()
|
||||
@@ -264,7 +277,12 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
|
||||
Err(TransactionError::Connection(err)) => Err(err)?,
|
||||
Err(TransactionError::Transaction(err)) => Err(err)?,
|
||||
};
|
||||
println!("Created Show: {}", flix_id.into_raw());
|
||||
println!(
|
||||
"Created Show: {} ({}) [{}]",
|
||||
title,
|
||||
year,
|
||||
flix_id.into_raw()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -498,10 +516,3 @@ pub async fn update(client: Client, database: &DatabaseConnection, command: Comm
|
||||
_ = command;
|
||||
unimplemented!("updates")
|
||||
}
|
||||
|
||||
pub async fn delete(client: Client, database: &DatabaseConnection, command: Command) -> Result<()> {
|
||||
_ = client;
|
||||
_ = database;
|
||||
_ = command;
|
||||
unimplemented!("deletions")
|
||||
}
|
||||
|
||||
@@ -215,18 +215,19 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let path = dir.path();
|
||||
if filetype.is_dir() {
|
||||
subdirs_to_scan.push(path);
|
||||
continue;
|
||||
@@ -236,7 +237,7 @@ impl Scanner {
|
||||
is_image_extension!() => {
|
||||
if poster_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicatePosterFile),
|
||||
};
|
||||
continue;
|
||||
@@ -248,7 +249,7 @@ impl Scanner {
|
||||
}
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,11 +60,13 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
@@ -72,18 +74,17 @@ impl Scanner {
|
||||
};
|
||||
if !filetype.is_file() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedNonFile),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = dir.path();
|
||||
match path.extension().and_then(OsStr::to_str) {
|
||||
is_media_extension!() => {
|
||||
if media_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicateMediaFile),
|
||||
};
|
||||
continue;
|
||||
@@ -97,7 +98,7 @@ impl Scanner {
|
||||
is_image_extension!() => {
|
||||
if poster_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicatePosterFile),
|
||||
};
|
||||
continue;
|
||||
@@ -109,7 +110,7 @@ impl Scanner {
|
||||
}
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -211,7 +211,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(|| {
|
||||
@@ -257,11 +257,13 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
@@ -271,11 +273,9 @@ impl Scanner {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir_path = dir.path();
|
||||
let Some(folder_name) = dir_path.file_name().and_then(OsStr::to_str)
|
||||
else {
|
||||
let Some(folder_name) = path.file_name().and_then(OsStr::to_str) else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
|
||||
@@ -56,11 +56,13 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
@@ -68,18 +70,17 @@ impl Scanner {
|
||||
};
|
||||
if !filetype.is_file() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedNonFile),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = dir.path();
|
||||
match path.extension().and_then(OsStr::to_str) {
|
||||
is_media_extension!() => {
|
||||
if media_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicateMediaFile),
|
||||
};
|
||||
continue;
|
||||
@@ -93,7 +94,7 @@ impl Scanner {
|
||||
is_image_extension!() => {
|
||||
if poster_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicatePosterFile),
|
||||
};
|
||||
continue;
|
||||
@@ -105,7 +106,7 @@ impl Scanner {
|
||||
}
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,18 +89,19 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let path = dir.path();
|
||||
if filetype.is_dir() {
|
||||
episode_dirs_to_scan.push(path);
|
||||
continue;
|
||||
@@ -110,7 +111,7 @@ impl Scanner {
|
||||
is_image_extension!() => {
|
||||
if poster_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicatePosterFile),
|
||||
};
|
||||
continue;
|
||||
@@ -122,7 +123,7 @@ impl Scanner {
|
||||
}
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
@@ -149,7 +150,7 @@ impl Scanner {
|
||||
for episode_dir in episode_dirs_to_scan {
|
||||
let Some(episode_dir_name) = episode_dir.file_name().and_then(OsStr::to_str) else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
@@ -157,14 +158,14 @@ impl Scanner {
|
||||
|
||||
let Some((_, s_e_str)) = episode_dir_name.split_once('S') else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
};
|
||||
let Some((s_str, e_str)) = s_e_str.split_once('E') else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
@@ -172,14 +173,14 @@ impl Scanner {
|
||||
|
||||
let Ok(season_number) = s_str.parse::<SeasonNumber>() else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
};
|
||||
if season_number != season {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::Inconsistent),
|
||||
};
|
||||
continue;
|
||||
@@ -191,14 +192,14 @@ impl Scanner {
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
};
|
||||
let Ok(episode_numbers) = EpisodeNumbers::try_from(episode_numbers.as_ref()) else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: episode_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
|
||||
@@ -107,18 +107,19 @@ impl Scanner {
|
||||
for await dir in ReadDirStream::new(dirs) {
|
||||
match dir {
|
||||
Ok(dir) => {
|
||||
let path = dir.path();
|
||||
|
||||
let filetype = match dir.file_type().await {
|
||||
Ok(filetype) => filetype,
|
||||
Err(err) => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::FileType(err)),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let path = dir.path();
|
||||
if filetype.is_dir() {
|
||||
season_dirs_to_scan.push(path);
|
||||
continue;
|
||||
@@ -128,7 +129,7 @@ impl Scanner {
|
||||
is_image_extension!() => {
|
||||
if poster_file_name.is_some() {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::DuplicatePosterFile),
|
||||
};
|
||||
continue;
|
||||
@@ -140,7 +141,7 @@ impl Scanner {
|
||||
}
|
||||
Some(_) | None => {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
event: Err(Error::UnexpectedFile),
|
||||
};
|
||||
}
|
||||
@@ -167,7 +168,7 @@ impl Scanner {
|
||||
for season_dir in season_dirs_to_scan {
|
||||
let Some(season_dir_name) = season_dir.file_name().and_then(OsStr::to_str) else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: season_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
@@ -178,7 +179,7 @@ impl Scanner {
|
||||
.map(|(_, s)| s.parse::<SeasonNumber>())
|
||||
else {
|
||||
yield Item {
|
||||
path: path.to_owned(),
|
||||
path: season_dir,
|
||||
event: Err(Error::UnexpectedFolder),
|
||||
};
|
||||
continue;
|
||||
|
||||
@@ -20,7 +20,7 @@ pub enum Error {
|
||||
}
|
||||
|
||||
/// A wrapper for handling single and multi-episode entries
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct EpisodeNumbers(RangeInclusive<EpisodeNumber>);
|
||||
|
||||
@@ -53,8 +53,27 @@ impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers {
|
||||
}
|
||||
|
||||
impl EpisodeNumbers {
|
||||
/// Create an [EpisodeNumbers] from a starting number and a count.
|
||||
/// `count` should be zero for single episodes.
|
||||
pub fn new(start: EpisodeNumber, count: u8) -> Self {
|
||||
Self(start..=start.saturating_add(count.into()))
|
||||
}
|
||||
|
||||
/// Get the range of episodes
|
||||
pub fn as_range(&self) -> &RangeInclusive<EpisodeNumber> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Render this [EpisodeNumbers] as a range. If only one episode is
|
||||
/// is present it renders as `01`, if multiple it renders as `01-02`
|
||||
pub fn range_string(&self) -> String {
|
||||
let start = self.0.start();
|
||||
let end = self.0.end();
|
||||
|
||||
if start == end {
|
||||
format!("{:02}", start)
|
||||
} else {
|
||||
format!("{:02}-{:02}", start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user