From 6eec67a0fdd4d855d81086b0ff4db894923461d1 Mon Sep 17 00:00:00 2001 From: David Skrundz Date: Wed, 1 Oct 2025 19:34:07 -0600 Subject: [PATCH] Model refinement --- Cargo.lock | 16 +++---- Cargo.toml | 14 +++--- README.md | 1 + crates/cli/Cargo.toml | 2 +- crates/db/Cargo.toml | 2 +- crates/db/src/entity/content/collections.rs | 32 ++++++++++++- crates/db/src/entity/content/episodes.rs | 36 ++++++++++++++- crates/db/src/entity/content/mod.rs | 46 ++++++++++--------- crates/db/src/entity/content/movies.rs | 34 +++++++++++++- crates/db/src/entity/content/seasons.rs | 32 ++++++++++++- crates/db/src/entity/content/shows.rs | 32 ++++++++++++- crates/db/src/entity/mod.rs | 21 ++++++--- crates/db/src/entity/watched/mod.rs | 6 +-- .../db/src/migration/m_000003/collections.rs | 4 +- crates/db/src/migration/m_000003/episodes.rs | 8 ++-- crates/db/src/migration/m_000003/movies.rs | 6 +-- crates/db/src/migration/m_000003/seasons.rs | 4 +- crates/db/src/migration/m_000003/shows.rs | 4 +- crates/flix/Cargo.toml | 2 +- crates/fs/Cargo.toml | 4 +- crates/fs/src/scanner/collection.rs | 26 +++++------ crates/fs/src/scanner/episode.rs | 6 +-- crates/fs/src/scanner/generic.rs | 32 +++++++------ crates/fs/src/scanner/season.rs | 27 ++++++----- crates/fs/src/scanner/show.rs | 16 +++---- crates/model/Cargo.toml | 2 +- crates/model/src/numbers.rs | 18 -------- crates/tmdb/Cargo.toml | 2 +- 28 files changed, 292 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bda8dfe..fc7e95f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,7 +489,7 @@ checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "flix" -version = "0.0.9" +version = "0.0.10" dependencies = [ "flix-db", "flix-fs", @@ -499,7 +499,7 @@ dependencies = [ [[package]] name = "flix-cli" -version = "0.0.9" +version = "0.0.10" dependencies = [ "anyhow", "chrono", @@ -515,7 +515,7 @@ dependencies = [ [[package]] name = "flix-db" -version = "0.0.9" +version = "0.0.10" dependencies = [ "chrono", "flix-model", @@ -528,7 +528,7 @@ dependencies = [ [[package]] name = "flix-fs" -version = "0.0.9" +version = "0.0.10" dependencies = [ "async-stream", "flix-model", @@ -540,7 +540,7 @@ dependencies = [ [[package]] name = "flix-model" -version = "0.0.9" +version = "0.0.10" dependencies = [ "seamantic", "serde", @@ -549,7 +549,7 @@ dependencies = [ [[package]] name = "flix-tmdb" -version = "0.0.9" +version = "0.0.10" dependencies = [ "chrono", "flix-model", @@ -1961,9 +1961,9 @@ dependencies = [ [[package]] name = "seamantic" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4b388ca1d150dd2f3cceff59e910543fe7c638f44a9cb6223ace53d06d38e" +checksum = "f4672c35f48459f4c16151a51ec6a1f244406fdf08e2e8ad8bcc6c78867d18af" dependencies = [ "sea-orm", "sea-orm-migration", diff --git a/Cargo.toml b/Cargo.toml index e136180..91ab681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,14 +35,14 @@ overflow-checks = true strip = "debuginfo" [workspace.dependencies] -flix = { path = "crates/flix", version = "=0.0.9", default-features = false } -flix-cli = { path = "crates/cli", version = "=0.0.9", default-features = false } -flix-db = { path = "crates/db", version = "=0.0.9", default-features = false } -flix-fs = { path = "crates/fs", version = "=0.0.9", default-features = false } -flix-model = { path = "crates/model", version = "=0.0.9", default-features = false } -flix-tmdb = { path = "crates/tmdb", version = "=0.0.9", default-features = false } +flix = { path = "crates/flix", version = "=0.0.10", default-features = false } +flix-cli = { path = "crates/cli", version = "=0.0.10", default-features = false } +flix-db = { path = "crates/db", version = "=0.0.10", default-features = false } +flix-fs = { path = "crates/fs", version = "=0.0.10", default-features = false } +flix-model = { path = "crates/model", version = "=0.0.10", default-features = false } +flix-tmdb = { path = "crates/tmdb", version = "=0.0.10", default-features = false } -seamantic = { version = "0.0.5", default-features = false } +seamantic = { version = "0.0.6", default-features = false } sea-orm = { version = "2.0.0-rc.7", default-features = false } sea-orm-migration = { version = "2.0.0-rc.7", default-features = false } diff --git a/README.md b/README.md index aa5c965..0dbad06 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,5 @@ Libraries and tools for dealing with media metadata - fmt: `cargo fmt --check` - docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` - install: `cargo install --path crates/cli` +- semver: `cargo semver-checks --all-features` - publish: `cargo publish --dry-run --workspace` diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 168b50d..ecd99bd 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix-cli" -version = "0.0.9" +version = "0.0.10" categories = ["command-line-utilities"] description = "CLI for interacting with a flix database" diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 45b419c..5008fea 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix-db" -version = "0.0.9" +version = "0.0.10" categories = [] description = "Types for storing persistent data about media" diff --git a/crates/db/src/entity/content/collections.rs b/crates/db/src/entity/content/collections.rs index addbfae..c7e074e 100644 --- a/crates/db/src/entity/content/collections.rs +++ b/crates/db/src/entity/content/collections.rs @@ -25,7 +25,7 @@ pub struct Model { /// The collection's directory pub directory: PathBytes, /// The collection's poster path - pub relative_poster_path: Option, + pub relative_poster_path: Option, } impl ActiveModelBehavior for ActiveModel {} @@ -51,6 +51,24 @@ pub enum Relation { on_delete = "Cascade" )] Library, + /// The media info for this collection + #[sea_orm( + belongs_to = "super::super::info::collections::Entity", + from = "Column::Id", + to = "super::super::info::collections::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + MediaInfo, + /// The watched info for this collection + #[sea_orm( + belongs_to = "super::super::watched::collections::Entity", + from = "Column::Id", + to = "super::super::watched::collections::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + WatchInfo, } impl Related for Entity { @@ -64,3 +82,15 @@ impl Related for Entity { Relation::Library.def() } } + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MediaInfo.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::WatchInfo.def() + } +} diff --git a/crates/db/src/entity/content/episodes.rs b/crates/db/src/entity/content/episodes.rs index 524b6e1..78d32ee 100644 --- a/crates/db/src/entity/content/episodes.rs +++ b/crates/db/src/entity/content/episodes.rs @@ -23,6 +23,8 @@ pub struct Model { /// The episode's number #[sea_orm(primary_key, auto_increment = false)] pub episode: EpisodeNumber, + /// The number of additional contained episodes + pub count: u8, /// The episode's slug pub slug: String, /// The episode's library @@ -30,9 +32,9 @@ pub struct Model { /// The episode's directory pub directory: PathBytes, /// The episode's media path - pub relative_media_path: PathBytes, + pub relative_media_path: String, /// The episode's poster path - pub relative_poster_path: Option, + pub relative_poster_path: Option, } impl ActiveModelBehavior for ActiveModel {} @@ -49,6 +51,24 @@ pub enum Relation { on_delete = "Cascade" )] Library, + /// The media info for this episode + #[sea_orm( + belongs_to = "super::super::info::seasons::Entity", + from = "(Column::Show, Column::Season)", + to = "(super::super::info::seasons::Column::Show, super::super::info::seasons::Column::Season)", + on_update = "Cascade", + on_delete = "Cascade" + )] + MediaInfo, + /// The watched info for this episode + #[sea_orm( + belongs_to = "super::super::watched::episodes::Entity", + from = "(Column::Show, Column::Season, Column::Episode)", + to = "(super::super::watched::episodes::Column::Show, super::super::watched::episodes::Column::Season, super::super::watched::episodes::Column::Episode)", + on_update = "Cascade", + on_delete = "Cascade" + )] + WatchInfo, } impl Related for Entity { @@ -56,3 +76,15 @@ impl Related for Entity { Relation::Library.def() } } + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MediaInfo.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::WatchInfo.def() + } +} diff --git a/crates/db/src/entity/content/mod.rs b/crates/db/src/entity/content/mod.rs index f799238..c8d4cac 100644 --- a/crates/db/src/entity/content/mod.rs +++ b/crates/db/src/entity/content/mod.rs @@ -74,7 +74,7 @@ mod tests { assert_eq!(model.slug, concat!("C/", $id).to_string()); assert_eq!(model.library, LibraryId::from_raw($lid)); assert_eq!(model.directory, Path::new(concat!("/C/", $id)).to_owned().into()); - assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, Path::new(concat!("C/Poster", $id)).to_owned().into() $(, $($skip),+)?)); + assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("C/Poster", $id).to_owned() $(, $($skip),+)?)); }; ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => { let model = assert_collection!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?) @@ -89,7 +89,7 @@ mod tests { slug: notsettable!(slug, concat!("C/", $id).to_string() $(, $($skip),+)?), library: notsettable!(library, LibraryId::from_raw($lid) $(, $($skip),+)?), directory: notsettable!(directory, Path::new(concat!("/C/", $id)).to_owned().into() $(, $($skip),+)?), - relative_poster_path: notsettable!(relative_poster_path, Some(Path::new(concat!("C/Poster", $id)).to_owned().into()) $(, $($skip),+)?), + relative_poster_path: notsettable!(relative_poster_path, Some(concat!("C/Poster", $id).to_owned()) $(, $($skip),+)?), }.insert($db).await }; } @@ -129,8 +129,8 @@ mod tests { assert_eq!(model.slug, concat!("M/", $id).to_string()); assert_eq!(model.library, LibraryId::from_raw($lid)); assert_eq!(model.directory, Path::new(concat!("/M/", $id)).to_owned().into()); - assert_eq!(model.relative_media_path, Path::new(concat!("M/Media", $id)).to_owned().into()); - assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, Path::new(concat!("M/Poster", $id)).to_owned().into() $(, $($skip),+)?)); + assert_eq!(model.relative_media_path, concat!("M/Media", $id)); + assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("M/Poster", $id).to_owned() $(, $($skip),+)?)); }; ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => { let model = assert_movie!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?) @@ -145,8 +145,8 @@ mod tests { slug: notsettable!(slug, concat!("M/", $id).to_string() $(, $($skip),+)?), library: notsettable!(library, LibraryId::from_raw($lid) $(, $($skip),+)?), directory: notsettable!(directory, Path::new(concat!("/M/", $id)).to_owned().into() $(, $($skip),+)?), - relative_media_path: notsettable!(relative_media_path, Path::new(concat!("M/Media", $id)).to_owned().into() $(, $($skip),+)?), - relative_poster_path: notsettable!(relative_poster_path, Some(Path::new(concat!("M/Poster", $id)).to_owned().into()) $(, $($skip),+)?), + relative_media_path: notsettable!(relative_media_path, concat!("M/Media", $id).to_owned() $(, $($skip),+)?), + relative_poster_path: notsettable!(relative_poster_path, Some(concat!("M/Poster", $id).to_owned()) $(, $($skip),+)?), }.insert($db).await }; } @@ -187,7 +187,7 @@ mod tests { assert_eq!(model.slug, concat!("S/", $id).to_string()); assert_eq!(model.library, LibraryId::from_raw($lid)); assert_eq!(model.directory, Path::new(concat!("/S/", $id)).to_owned().into()); - assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, Path::new(concat!("S/Poster", $id)).to_owned().into() $(, $($skip),+)?)); + assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("S/Poster", $id).to_owned() $(, $($skip),+)?)); }; ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => { let model = assert_show!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?) @@ -202,7 +202,7 @@ mod tests { slug: notsettable!(slug, concat!("S/", $id).to_string() $(, $($skip),+)?), library: notsettable!(library, LibraryId::from_raw($lid) $(, $($skip),+)?), directory: notsettable!(directory, Path::new(concat!("/S/", $id)).to_owned().into() $(, $($skip),+)?), - relative_poster_path: notsettable!(relative_poster_path, Some(Path::new(concat!("S/Poster", $id)).to_owned().into()) $(, $($skip),+)?), + relative_poster_path: notsettable!(relative_poster_path, Some(concat!("S/Poster", $id).to_owned()) $(, $($skip),+)?), }.insert($db).await }; } @@ -242,7 +242,7 @@ mod tests { assert_eq!(model.slug, concat!("S/S", $id).to_string()); assert_eq!(model.library, LibraryId::from_raw($lid)); assert_eq!(model.directory, Path::new(concat!("/S/S", $id)).to_owned().into()); - assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, Path::new(concat!("S/S/Poster", $id)).to_owned().into() $(, $($skip),+)?)); + assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("S/S/Poster", $id).to_owned() $(, $($skip),+)?)); }; ($db:expr, $id:literal, $season:literal, $lid:literal, $error:ident $(; $($skip:ident),+)?) => { let model = assert_season!(@insert, $db, $id, $season, $lid $(; $($skip),+)?) @@ -257,7 +257,7 @@ mod tests { slug: notsettable!(slug, concat!("S/S", $id).to_string() $(, $($skip),+)?), library: notsettable!(library, LibraryId::from_raw($lid) $(, $($skip),+)?), directory: notsettable!(directory, Path::new(concat!("/S/S", $id)).to_owned().into() $(, $($skip),+)?), - relative_poster_path: notsettable!(relative_poster_path, Some(Path::new(concat!("S/S/Poster", $id)).to_owned().into()) $(, $($skip),+)?), + relative_poster_path: notsettable!(relative_poster_path, Some(concat!("S/S/Poster", $id).to_owned()) $(, $($skip),+)?), }.insert($db).await }; } @@ -292,8 +292,8 @@ mod tests { assert_eq!(model.slug, concat!("S/S/E", $id).to_string()); assert_eq!(model.library, LibraryId::from_raw($lid)); assert_eq!(model.directory, Path::new(concat!("/S/S/E", $id)).to_owned().into()); - assert_eq!(model.relative_media_path, Path::new(concat!("E/Media", $id)).to_owned().into()); - assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, Path::new(concat!("S/S/E/Poster", $id)).to_owned().into() $(, $($skip),+)?)); + assert_eq!(model.relative_media_path, concat!("E/Media", $id)); + assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("S/S/E/Poster", $id).to_owned() $(, $($skip),+)?)); }; ($db:expr, $id:literal, $season:literal, $episode:literal, $lid:literal, $error:ident $(; $($skip:ident),+)?) => { let model = assert_episode!(@insert, $db, $id, $season, $episode, $lid $(; $($skip),+)?) @@ -306,11 +306,12 @@ mod tests { show: notsettable!(id, ShowId::from_raw($id) $(, $($skip),+)?), season: notsettable!(season, $season $(, $($skip),+)?), episode: notsettable!(episode, $episode $(, $($skip),+)?), + count: notsettable!(count, 0 $(, $($skip),+)?), slug: notsettable!(slug, concat!("S/S/E", $id).to_string() $(, $($skip),+)?), library: notsettable!(library, LibraryId::from_raw($lid) $(, $($skip),+)?), directory: notsettable!(directory, Path::new(concat!("/S/S/E", $id)).to_owned().into() $(, $($skip),+)?), - relative_media_path: notsettable!(relative_media_path, Path::new(concat!("E/Media", $id)).to_owned().into() $(, $($skip),+)?), - relative_poster_path: notsettable!(relative_poster_path, Some(Path::new(concat!("S/S/E/Poster", $id)).to_owned().into()) $(, $($skip),+)?), + relative_media_path: notsettable!(relative_media_path, concat!("E/Media", $id).to_owned() $(, $($skip),+)?), + relative_poster_path: notsettable!(relative_poster_path, Some(concat!("S/S/E/Poster", $id).to_owned()) $(, $($skip),+)?), }.insert($db).await }; } @@ -320,9 +321,9 @@ mod tests { make_flix_episode!(&db, 1, 1, 2); make_flix_episode!(&db, 2, 1, 1); make_flix_episode!(&db, 3, 1, 1); - make_flix_show!(&db, 10); - make_flix_season!(&db, 10, 1); - make_flix_episode!(&db, 10, 1, 1); + make_flix_show!(&db, 11); + make_flix_season!(&db, 11, 1); + make_flix_episode!(&db, 11, 1, 1); assert_episode!(&db, 1, 1, 1, 1, Success); assert_episode!(&db, 1, 1, 2, 1, Success); @@ -331,10 +332,11 @@ mod tests { assert_episode!(&db, 3, 1, 1, 1, Success; show); assert_episode!(&db, 4, 1, 1, 1, NotNullViolation; season); assert_episode!(&db, 5, 1, 1, 1, NotNullViolation; episode); - assert_episode!(&db, 6, 1, 1, 1, NotNullViolation; slug); - assert_episode!(&db, 7, 1, 1, 1, NotNullViolation; library); - assert_episode!(&db, 8, 1, 1, 1, NotNullViolation; directory); - assert_episode!(&db, 9, 1, 1, 1, NotNullViolation; relative_media_path); - assert_episode!(&db, 10, 1, 1, 1, Success; relative_poster_path); + assert_episode!(&db, 6, 1, 1, 1, NotNullViolation; count); + assert_episode!(&db, 7, 1, 1, 1, NotNullViolation; slug); + assert_episode!(&db, 8, 1, 1, 1, NotNullViolation; library); + assert_episode!(&db, 9, 1, 1, 1, NotNullViolation; directory); + assert_episode!(&db, 10, 1, 1, 1, NotNullViolation; relative_media_path); + assert_episode!(&db, 11, 1, 1, 1, Success; relative_poster_path); } } diff --git a/crates/db/src/entity/content/movies.rs b/crates/db/src/entity/content/movies.rs index 4dc57be..3f2cc00 100644 --- a/crates/db/src/entity/content/movies.rs +++ b/crates/db/src/entity/content/movies.rs @@ -25,9 +25,9 @@ pub struct Model { /// The movie's directory pub directory: PathBytes, /// The movie's media path - pub relative_media_path: PathBytes, + pub relative_media_path: String, /// The movie's poster path - pub relative_poster_path: Option, + pub relative_poster_path: Option, } impl ActiveModelBehavior for ActiveModel {} @@ -53,6 +53,24 @@ pub enum Relation { on_delete = "Cascade" )] Library, + /// The media info for this movie + #[sea_orm( + belongs_to = "super::super::info::movies::Entity", + from = "Column::Id", + to = "super::super::info::movies::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + MediaInfo, + /// The watched info for this movie + #[sea_orm( + belongs_to = "super::super::watched::movies::Entity", + from = "Column::Id", + to = "super::super::watched::movies::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + WatchInfo, } impl Related for Entity { @@ -66,3 +84,15 @@ impl Related for Entity { Relation::Library.def() } } + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MediaInfo.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::WatchInfo.def() + } +} diff --git a/crates/db/src/entity/content/seasons.rs b/crates/db/src/entity/content/seasons.rs index 038b8db..2a74029 100644 --- a/crates/db/src/entity/content/seasons.rs +++ b/crates/db/src/entity/content/seasons.rs @@ -27,7 +27,7 @@ pub struct Model { /// The season's directory pub directory: PathBytes, /// The season's poster path - pub relative_poster_path: Option, + pub relative_poster_path: Option, } impl ActiveModelBehavior for ActiveModel {} @@ -44,6 +44,24 @@ pub enum Relation { on_delete = "Cascade" )] Library, + /// The media info for this show + #[sea_orm( + belongs_to = "super::super::info::seasons::Entity", + from = "(Column::Show, Column::Season)", + to = "(super::super::info::seasons::Column::Show, super::super::info::seasons::Column::Season)", + on_update = "Cascade", + on_delete = "Cascade" + )] + MediaInfo, + /// The watched info for this show + #[sea_orm( + belongs_to = "super::super::watched::seasons::Entity", + from = "(Column::Show, Column::Season)", + to = "(super::super::watched::seasons::Column::Show, super::super::watched::seasons::Column::Season)", + on_update = "Cascade", + on_delete = "Cascade" + )] + WatchInfo, } impl Related for Entity { @@ -51,3 +69,15 @@ impl Related for Entity { Relation::Library.def() } } + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MediaInfo.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::WatchInfo.def() + } +} diff --git a/crates/db/src/entity/content/shows.rs b/crates/db/src/entity/content/shows.rs index 96ea4c3..6dd85e3 100644 --- a/crates/db/src/entity/content/shows.rs +++ b/crates/db/src/entity/content/shows.rs @@ -25,7 +25,7 @@ pub struct Model { /// The show's directory pub directory: PathBytes, /// The show's poster path - pub relative_poster_path: Option, + pub relative_poster_path: Option, } impl ActiveModelBehavior for ActiveModel {} @@ -51,6 +51,24 @@ pub enum Relation { on_delete = "Cascade" )] Library, + /// The media info for this show + #[sea_orm( + belongs_to = "super::super::info::shows::Entity", + from = "Column::Id", + to = "super::super::info::shows::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + MediaInfo, + /// The watched info for this show + #[sea_orm( + belongs_to = "super::super::watched::shows::Entity", + from = "Column::Id", + to = "super::super::watched::shows::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + WatchInfo, } impl Related for Entity { @@ -64,3 +82,15 @@ impl Related for Entity { Relation::Library.def() } } + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MediaInfo.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::WatchInfo.def() + } +} diff --git a/crates/db/src/entity/mod.rs b/crates/db/src/entity/mod.rs index d24df5a..7810fdb 100644 --- a/crates/db/src/entity/mod.rs +++ b/crates/db/src/entity/mod.rs @@ -108,7 +108,7 @@ mod tests { slug: Set(::std::string::String::new()), library: Set(::flix_model::id::LibraryId::from_raw($lid)), directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(None), + relative_poster_path: Set(::core::option::Option::None), } .insert($db) .await @@ -144,8 +144,8 @@ mod tests { slug: Set(::std::string::String::new()), library: Set(::flix_model::id::LibraryId::from_raw($lid)), directory: Set(::std::path::PathBuf::new().into()), - relative_media_path: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(None), + relative_media_path: Set(::std::string::String::new()), + relative_poster_path: Set(::core::option::Option::None), } .insert($db) .await @@ -181,7 +181,7 @@ mod tests { slug: Set(::std::string::String::new()), library: Set(::flix_model::id::LibraryId::from_raw($lid)), directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(None), + relative_poster_path: Set(::core::option::Option::None), } .insert($db) .await @@ -216,7 +216,7 @@ mod tests { slug: Set(::std::string::String::new()), library: Set(::flix_model::id::LibraryId::from_raw($lid)), directory: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(None), + relative_poster_path: Set(::core::option::Option::None), } .insert($db) .await @@ -245,16 +245,23 @@ mod tests { pub(super) use make_flix_episode; macro_rules! have_episode { ($db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal) => { + have_episode!(@make, $db, $lid, $show, $season, $episode, 0); + }; + ($db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal, >1) => { + have_episode!(@make, $db, $lid, $show, $season, $episode, 1); + }; + (@make, $db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal, $count:literal) => { $crate::entity::tests::make_flix_episode!($db, $show, $season, $episode); $crate::entity::content::episodes::ActiveModel { show: Set(::flix_model::id::ShowId::from_raw($show)), season: Set($season), episode: Set($episode), + count: Set($count), slug: Set(::std::string::String::new()), library: Set(::flix_model::id::LibraryId::from_raw($lid)), directory: Set(::std::path::PathBuf::new().into()), - relative_media_path: Set(::std::path::PathBuf::new().into()), - relative_poster_path: Set(None), + relative_media_path: Set(::std::string::String::new()), + relative_poster_path: Set(::core::option::Option::None), } .insert($db) .await diff --git a/crates/db/src/entity/watched/mod.rs b/crates/db/src/entity/watched/mod.rs index 8f16927..5f73b58 100644 --- a/crates/db/src/entity/watched/mod.rs +++ b/crates/db/src/entity/watched/mod.rs @@ -181,12 +181,10 @@ mod tests { have_episode!(&db, 1, 1, 1, 1); assert_episode!(&db, 1, 1, 1, 1, Success); assert_episode!(&db, 1, 1, 1, 2, Success); - have_episode!(&db, 1, 1, 1, 2); + have_episode!(&db, 1, 1, 1, 2, >1); // Covers 2 and 3 + make_flix_episode!(&db, 1, 1, 3); assert_episode!(&db, 1, 1, 2, 1, Success); assert_episode!(&db, 1, 1, 2, 2, Success); - have_episode!(&db, 1, 1, 1, 3); - assert_episode!(&db, 1, 1, 3, 1, Success); - assert_episode!(&db, 1, 1, 3, 2, Success); have_season!(&db, 1, 1, 2); have_episode!(&db, 1, 1, 2, 1); assert_episode!(&db, 1, 2, 1, 1, Success); diff --git a/crates/db/src/migration/m_000003/collections.rs b/crates/db/src/migration/m_000003/collections.rs index 2aae68a..3d41eba 100644 --- a/crates/db/src/migration/m_000003/collections.rs +++ b/crates/db/src/migration/m_000003/collections.rs @@ -4,7 +4,7 @@ use sea_orm::sea_query; use sea_orm::sea_query::{ForeignKeyCreateStatement, Table}; use sea_orm::{DbErr, Iden}; use sea_orm_migration::SchemaManager; -use sea_orm_migration::schema::{binary, binary_null, integer, integer_null, string}; +use sea_orm_migration::schema::{binary, integer, integer_null, string, string_null}; use crate::migration::m_000001::FlixInfoCollections; use crate::migration::m_000003::FlixLibraries; @@ -30,7 +30,7 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { .col(string(FlixCollections::Slug)) .col(integer(FlixCollections::Library)) .col(binary(FlixCollections::Directory)) - .col(binary_null(FlixCollections::RelativePosterPath)) + .col(string_null(FlixCollections::RelativePosterPath)) .foreign_key( ForeignKeyCreateStatement::new() .name("fk-flix_collections-id") diff --git a/crates/db/src/migration/m_000003/episodes.rs b/crates/db/src/migration/m_000003/episodes.rs index 7a70291..4385021 100644 --- a/crates/db/src/migration/m_000003/episodes.rs +++ b/crates/db/src/migration/m_000003/episodes.rs @@ -2,7 +2,7 @@ use sea_orm::sea_query; use sea_orm::sea_query::{ForeignKeyCreateStatement, Index, Table}; use sea_orm::{DbErr, Iden}; use sea_orm_migration::SchemaManager; -use sea_orm_migration::schema::{binary, binary_null, integer, string}; +use sea_orm_migration::schema::{binary, integer, string, string_null}; use crate::migration::m_000001::FlixInfoEpisodes; use crate::migration::m_000003::FlixLibraries; @@ -13,6 +13,7 @@ pub enum FlixEpisodes { Show, Season, Episode, + Count, Slug, Library, Directory, @@ -28,11 +29,12 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { .col(integer(FlixEpisodes::Show)) .col(integer(FlixEpisodes::Season)) .col(integer(FlixEpisodes::Episode)) + .col(integer(FlixEpisodes::Count)) .col(string(FlixEpisodes::Slug)) .col(integer(FlixEpisodes::Library)) .col(binary(FlixEpisodes::Directory)) - .col(binary(FlixEpisodes::RelativeMediaPath)) - .col(binary_null(FlixEpisodes::RelativePosterPath)) + .col(string(FlixEpisodes::RelativeMediaPath)) + .col(string_null(FlixEpisodes::RelativePosterPath)) .primary_key( Index::create() .col(FlixEpisodes::Show) diff --git a/crates/db/src/migration/m_000003/movies.rs b/crates/db/src/migration/m_000003/movies.rs index 59f5264..5523c9d 100644 --- a/crates/db/src/migration/m_000003/movies.rs +++ b/crates/db/src/migration/m_000003/movies.rs @@ -4,7 +4,7 @@ use sea_orm::sea_query; use sea_orm::sea_query::{ForeignKeyCreateStatement, Table}; use sea_orm::{DbErr, Iden}; use sea_orm_migration::SchemaManager; -use sea_orm_migration::schema::{binary, binary_null, integer, integer_null, string}; +use sea_orm_migration::schema::{binary, integer, integer_null, string, string_null}; use crate::migration::m_000001::FlixInfoMovies; use crate::migration::m_000003::{FlixCollections, FlixLibraries}; @@ -31,8 +31,8 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { .col(string(FlixMovies::Slug)) .col(integer(FlixMovies::Library)) .col(binary(FlixMovies::Directory)) - .col(binary(FlixMovies::RelativeMediaPath)) - .col(binary_null(FlixMovies::RelativePosterPath)) + .col(string(FlixMovies::RelativeMediaPath)) + .col(string_null(FlixMovies::RelativePosterPath)) .foreign_key( ForeignKeyCreateStatement::new() .name("fk-flix_movies-id") diff --git a/crates/db/src/migration/m_000003/seasons.rs b/crates/db/src/migration/m_000003/seasons.rs index be51624..d29ca1b 100644 --- a/crates/db/src/migration/m_000003/seasons.rs +++ b/crates/db/src/migration/m_000003/seasons.rs @@ -2,7 +2,7 @@ use sea_orm::sea_query; use sea_orm::sea_query::{ForeignKeyCreateStatement, Index, Table}; use sea_orm::{DbErr, Iden}; use sea_orm_migration::SchemaManager; -use sea_orm_migration::schema::{binary, binary_null, integer, string}; +use sea_orm_migration::schema::{binary, integer, string, string_null}; use crate::migration::m_000001::FlixInfoSeasons; use crate::migration::m_000003::FlixLibraries; @@ -28,7 +28,7 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { .col(string(FlixSeasons::Slug)) .col(integer(FlixSeasons::Library)) .col(binary(FlixSeasons::Directory)) - .col(binary_null(FlixSeasons::RelativePosterPath)) + .col(string_null(FlixSeasons::RelativePosterPath)) .primary_key( Index::create() .col(FlixSeasons::Show) diff --git a/crates/db/src/migration/m_000003/shows.rs b/crates/db/src/migration/m_000003/shows.rs index 65385a9..8095515 100644 --- a/crates/db/src/migration/m_000003/shows.rs +++ b/crates/db/src/migration/m_000003/shows.rs @@ -4,7 +4,7 @@ use sea_orm::sea_query; use sea_orm::sea_query::{ForeignKeyCreateStatement, Table}; use sea_orm::{DbErr, Iden}; use sea_orm_migration::SchemaManager; -use sea_orm_migration::schema::{binary, binary_null, integer, integer_null, string}; +use sea_orm_migration::schema::{binary, integer, integer_null, string, string_null}; use crate::migration::m_000001::FlixInfoShows; use crate::migration::m_000003::{FlixCollections, FlixLibraries}; @@ -30,7 +30,7 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> { .col(string(FlixShows::Slug)) .col(integer(FlixShows::Library)) .col(binary(FlixShows::Directory)) - .col(binary_null(FlixShows::RelativePosterPath)) + .col(string_null(FlixShows::RelativePosterPath)) .foreign_key( ForeignKeyCreateStatement::new() .name("fk-flix_shows-id") diff --git a/crates/flix/Cargo.toml b/crates/flix/Cargo.toml index 915764e..a96f6c1 100644 --- a/crates/flix/Cargo.toml +++ b/crates/flix/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix" -version = "0.0.9" +version = "0.0.10" categories = [] description = "Mechanisms for interacting with flix media" diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 2b70582..fe851d7 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix-fs" -version = "0.0.9" +version = "0.0.10" categories = [] description = "Filesystem scanner for flix media" @@ -22,7 +22,7 @@ workspace = true flix-model = { workspace = true } async-stream = { workspace = true } -regex = { workspace = true } +regex = { workspace = true, features = ["std", "perf"] } thiserror = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true, features = ["fs"] } diff --git a/crates/fs/src/scanner/collection.rs b/crates/fs/src/scanner/collection.rs index a854825..5e331db 100644 --- a/crates/fs/src/scanner/collection.rs +++ b/crates/fs/src/scanner/collection.rs @@ -47,17 +47,17 @@ pub enum Scanner { Show { /// The ID of the parent collection (if any) parent: Option, - /// The ID of the show this episode belongs to + /// The ID of the show id: ShowId, /// The file name of the poster file poster_file_name: Option, }, /// A scanned episode Season { - /// The ID of the show this episode belongs to + /// The ID of the show this season belongs to show: ShowId, - /// The season this episode belongs to - number: SeasonNumber, + /// The number of this season + season: SeasonNumber, /// The file name of the poster file poster_file_name: Option, }, @@ -68,7 +68,7 @@ pub enum Scanner { /// The season this episode belongs to season: SeasonNumber, /// The number(s) of this episode - number: EpisodeNumbers, + episode: EpisodeNumbers, /// The file name of the media file media_file_name: String, /// The file name of the poster file @@ -108,23 +108,23 @@ impl From for Scanner { }, show::Scanner::Season { show, - number, + season, poster_file_name, } => Self::Season { show, - number, + season, poster_file_name, }, show::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, @@ -166,23 +166,23 @@ impl From for Scanner { }, generic::Scanner::Season { show, - number, + season, poster_file_name, } => Self::Season { show, - number, + season, poster_file_name, }, generic::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, diff --git a/crates/fs/src/scanner/episode.rs b/crates/fs/src/scanner/episode.rs index 1112a6e..c00b070 100644 --- a/crates/fs/src/scanner/episode.rs +++ b/crates/fs/src/scanner/episode.rs @@ -26,7 +26,7 @@ pub enum Scanner { /// The season this episode belongs to season: SeasonNumber, /// The number(s) of this episode - number: EpisodeNumbers, + episode: EpisodeNumbers, /// The file name of the media file media_file_name: String, /// The file name of the poster file @@ -40,7 +40,7 @@ impl Scanner { path: &Path, show: ShowId, season: SeasonNumber, - number: EpisodeNumbers, + episode: EpisodeNumbers, ) -> impl Stream { stream!({ let dirs = match fs::read_dir(path).await { @@ -137,7 +137,7 @@ impl Scanner { event: Ok(Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }), diff --git a/crates/fs/src/scanner/generic.rs b/crates/fs/src/scanner/generic.rs index f4e7b22..1caa3db 100644 --- a/crates/fs/src/scanner/generic.rs +++ b/crates/fs/src/scanner/generic.rs @@ -51,17 +51,17 @@ pub enum Scanner { Show { /// The ID of the parent collection (if any) parent: Option, - /// The ID of the show this episode belongs to + /// The ID of the show id: ShowId, /// The file name of the poster file poster_file_name: Option, }, /// A scanned episode Season { - /// The ID of the show this episode belongs to + /// The ID of the show this season belongs to show: ShowId, /// The season this episode belongs to - number: SeasonNumber, + season: SeasonNumber, /// The file name of the poster file poster_file_name: Option, }, @@ -72,7 +72,7 @@ pub enum Scanner { /// The season this episode belongs to season: SeasonNumber, /// The number(s) of this episode - number: EpisodeNumbers, + episode: EpisodeNumbers, /// The file name of the media file media_file_name: String, /// The file name of the poster file @@ -114,23 +114,23 @@ impl From for Scanner { }, collection::Scanner::Season { show, - number, + season, poster_file_name, } => Self::Season { show, - number, + season, poster_file_name, }, collection::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, @@ -170,23 +170,23 @@ impl From for Scanner { }, show::Scanner::Season { show, - number, + season, poster_file_name, } => Self::Season { show, - number, + season, poster_file_name, }, show::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, @@ -211,10 +211,12 @@ impl Scanner { } let media_folder_re = MEDIA_FOLDER_REGEX.get_or_init(|| { - Regex::new(r"^[\w ]+ \(\d+\) \[\d+\]$").unwrap_or_else(|_| panic!("regex is invalid")) + 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(|| { + Regex::new(r"^S[[:digit:]]+$").unwrap_or_else(|err| panic!("regex is invalid: {err}")) }); - let season_folder_re = SEASON_FOLDER_REGEX - .get_or_init(|| Regex::new(r"^S\d+$").unwrap_or_else(|_| panic!("regex is invalid"))); stream!({ let Some(dir_name) = path.file_name().and_then(OsStr::to_str) else { diff --git a/crates/fs/src/scanner/season.rs b/crates/fs/src/scanner/season.rs index faf4592..84501f0 100644 --- a/crates/fs/src/scanner/season.rs +++ b/crates/fs/src/scanner/season.rs @@ -22,10 +22,10 @@ pub type Item = crate::Item; pub enum Scanner { /// A scanned episode Season { - /// The ID of the show this episode belongs to + /// The ID of the show this season belongs to show: ShowId, /// The season this episode belongs to - number: SeasonNumber, + season: SeasonNumber, /// The file name of the poster file poster_file_name: Option, }, @@ -36,7 +36,7 @@ pub enum Scanner { /// The season this episode belongs to season: SeasonNumber, /// The number(s) of this episode - number: EpisodeNumbers, + episode: EpisodeNumbers, /// The file name of the media file media_file_name: String, /// The file name of the poster file @@ -50,13 +50,13 @@ impl From for Scanner { episode::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, @@ -69,7 +69,7 @@ impl Scanner { pub fn scan_season( path: &Path, show: ShowId, - number: SeasonNumber, + season: SeasonNumber, ) -> impl Stream { stream!({ let dirs = match fs::read_dir(path).await { @@ -141,7 +141,7 @@ impl Scanner { path: path.to_owned(), event: Ok(Self::Season { show, - number, + season, poster_file_name, }), }; @@ -170,14 +170,14 @@ impl Scanner { continue; }; - let Ok(season) = s_str.parse::() else { + let Ok(season_number) = s_str.parse::() else { yield Item { path: path.to_owned(), event: Err(Error::UnexpectedFolder), }; continue; }; - if season != number { + if season_number != season { yield Item { path: path.to_owned(), event: Err(Error::Inconsistent), @@ -204,9 +204,12 @@ impl Scanner { continue; }; - for await event in - episode::Scanner::scan_episode(&episode_dir, show, number, episode_numbers) - { + for await event in episode::Scanner::scan_episode( + &episode_dir, + show, + season_number, + episode_numbers, + ) { yield event.map(|e| e.into()); } } diff --git a/crates/fs/src/scanner/show.rs b/crates/fs/src/scanner/show.rs index bfeee18..bafb8ad 100644 --- a/crates/fs/src/scanner/show.rs +++ b/crates/fs/src/scanner/show.rs @@ -24,17 +24,17 @@ pub enum Scanner { Show { /// The ID of the parent collection (if any) parent: Option, - /// The ID of the show this episode belongs to + /// The ID of the show id: ShowId, /// The file name of the poster file poster_file_name: Option, }, /// A scanned episode Season { - /// The ID of the show this episode belongs to + /// The ID of the show this season belongs to show: ShowId, /// The season this episode belongs to - number: SeasonNumber, + season: SeasonNumber, /// The file name of the poster file poster_file_name: Option, }, @@ -45,7 +45,7 @@ pub enum Scanner { /// The season this episode belongs to season: SeasonNumber, /// The number(s) of this episode - number: EpisodeNumbers, + episode: EpisodeNumbers, /// The file name of the media file media_file_name: String, /// The file name of the poster file @@ -58,23 +58,23 @@ impl From for Scanner { match value { season::Scanner::Season { show, - number, + season, poster_file_name, } => Self::Season { show, - number, + season, poster_file_name, }, season::Scanner::Episode { show, season, - number, + episode, media_file_name, poster_file_name, } => Self::Episode { show, season, - number, + episode, media_file_name, poster_file_name, }, diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index 7b55394..a750199 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix-model" -version = "0.0.9" +version = "0.0.10" categories = [] description = "Core types for flix data" diff --git a/crates/model/src/numbers.rs b/crates/model/src/numbers.rs index f1ce915..6f3cd1f 100644 --- a/crates/model/src/numbers.rs +++ b/crates/model/src/numbers.rs @@ -58,21 +58,3 @@ impl EpisodeNumbers { &self.0 } } - -// impl EpisodeNumbers { -// /// Get the primary episode number of this episode -// pub fn primary_episode_number(&self) -> Option { -// match self { -// EpisodeNumbers::Single { number } => Some(*number), -// EpisodeNumbers::Multiple { numbers } => numbers.first().copied(), -// } -// } - -// /// Get additional episode numbers of this episode -// pub fn additional_episode_numbers(&self) -> &[EpisodeNumber] { -// match self { -// EpisodeNumbers::Single { number: _ } => &[], -// EpisodeNumbers::Multiple { numbers } => numbers.get(1..).unwrap_or(&[]), -// } -// } -// } diff --git a/crates/tmdb/Cargo.toml b/crates/tmdb/Cargo.toml index 6eb1a2a..8293150 100644 --- a/crates/tmdb/Cargo.toml +++ b/crates/tmdb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flix-tmdb" -version = "0.0.9" +version = "0.0.10" categories = [] description = "Clients and models for fetching data from TMDB"