4 Commits

Author SHA1 Message Date
davidskrundz dd688fdc83 Update sea-orm rc version 2025-10-28 23:07:32 -06:00
davidskrundz 0d34174a13 Update sea-orm rc version 2025-10-13 23:40:04 -06:00
davidskrundz 8411c75377 Fix critical relation bug 2025-10-02 09:52:25 -07:00
davidskrundz 6eec67a0fd Model refinement 2025-10-01 19:34:07 -06:00
38 changed files with 602 additions and 514 deletions
Generated
+292 -353
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -6,7 +6,7 @@ resolver = "2"
authors = []
edition = "2024"
license-file = "LICENSE.md"
rust-version = "1.85.0"
rust-version = "1.88.0"
[workspace.lints.rust]
arithmetic_overflow = "forbid"
@@ -35,17 +35,17 @@ 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.12", default-features = false }
flix-cli = { path = "crates/cli", version = "=0.0.12", default-features = false }
flix-db = { path = "crates/db", version = "=0.0.12", default-features = false }
flix-fs = { path = "crates/fs", version = "=0.0.12", default-features = false }
flix-model = { path = "crates/model", version = "=0.0.12", default-features = false }
flix-tmdb = { path = "crates/tmdb", version = "=0.0.12", default-features = false }
seamantic = { version = "0.0.5", default-features = false }
seamantic = { version = "0.0.9", 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 }
sea-orm = { version = "2.0.0-rc.16", default-features = false }
sea-orm-migration = { version = "2.0.0-rc.16", default-features = false }
anyhow = { version = "^1", default-features = false }
async-stream = { version = "^0.3", default-features = false }
+2 -1
View File
@@ -7,8 +7,9 @@ Libraries and tools for dealing with media metadata
- build: `cargo hack --feature-powerset build`
- clippy: `cargo hack --feature-powerset clippy -- -D warnings`
- test: `cargo hack --feature-powerset test`
- test old: `cargo +1.85 hack --feature-powerset test`
- test old: `cargo +1.88 hack --feature-powerset test`
- 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`
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-cli"
version = "0.0.9"
version = "0.0.12"
categories = ["command-line-utilities"]
description = "CLI for interacting with a flix database"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-db"
version = "0.0.9"
version = "0.0.12"
categories = []
description = "Types for storing persistent data about media"
+1 -1
View File
@@ -7,7 +7,7 @@ use sea_orm_migration::MigratorTrait as _;
pub struct Connection(DatabaseConnection);
impl Connection {
/// Helper function for apllying database migrations while wrapping a
/// Helper function for applying database migrations while wrapping a
/// [DatabaseConnection] in a newtype
pub async fn try_from(database: DatabaseConnection) -> Result<Self, DbErr> {
crate::migration::Migrator::up(&database, None).await?;
+31 -1
View File
@@ -25,7 +25,7 @@ pub struct Model {
/// The collection's directory
pub directory: PathBytes,
/// The collection's poster path
pub relative_poster_path: Option<PathBytes>,
pub relative_poster_path: Option<String>,
}
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<super::collections::Entity> for Entity {
@@ -64,3 +82,15 @@ impl Related<super::libraries::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::super::info::collections::Entity> for Entity {
fn to() -> RelationDef {
Relation::MediaInfo.def()
}
}
impl Related<super::super::watched::collections::Entity> for Entity {
fn to() -> RelationDef {
Relation::WatchInfo.def()
}
}
+34 -2
View File
@@ -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<PathBytes>,
pub relative_poster_path: Option<String>,
}
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::episodes::Entity",
from = "(Column::Show, Column::Season, Column::Episode)",
to = "(super::super::info::episodes::Column::Show, super::super::info::episodes::Column::Season, super::super::info::episodes::Column::Episode)",
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<super::libraries::Entity> for Entity {
@@ -56,3 +76,15 @@ impl Related<super::libraries::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::super::info::episodes::Entity> for Entity {
fn to() -> RelationDef {
Relation::MediaInfo.def()
}
}
impl Related<super::super::watched::episodes::Entity> for Entity {
fn to() -> RelationDef {
Relation::WatchInfo.def()
}
}
+24 -22
View File
@@ -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);
}
}
+32 -2
View File
@@ -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<PathBytes>,
pub relative_poster_path: Option<String>,
}
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<super::collections::Entity> for Entity {
@@ -66,3 +84,15 @@ impl Related<super::libraries::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::super::info::movies::Entity> for Entity {
fn to() -> RelationDef {
Relation::MediaInfo.def()
}
}
impl Related<super::super::watched::movies::Entity> for Entity {
fn to() -> RelationDef {
Relation::WatchInfo.def()
}
}
+31 -1
View File
@@ -27,7 +27,7 @@ pub struct Model {
/// The season's directory
pub directory: PathBytes,
/// The season's poster path
pub relative_poster_path: Option<PathBytes>,
pub relative_poster_path: Option<String>,
}
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<super::libraries::Entity> for Entity {
@@ -51,3 +69,15 @@ impl Related<super::libraries::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::super::info::seasons::Entity> for Entity {
fn to() -> RelationDef {
Relation::MediaInfo.def()
}
}
impl Related<super::super::watched::seasons::Entity> for Entity {
fn to() -> RelationDef {
Relation::WatchInfo.def()
}
}
+31 -1
View File
@@ -25,7 +25,7 @@ pub struct Model {
/// The show's directory
pub directory: PathBytes,
/// The show's poster path
pub relative_poster_path: Option<PathBytes>,
pub relative_poster_path: Option<String>,
}
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<super::collections::Entity> for Entity {
@@ -64,3 +82,15 @@ impl Related<super::libraries::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::super::info::shows::Entity> for Entity {
fn to() -> RelationDef {
Relation::MediaInfo.def()
}
}
impl Related<super::super::watched::shows::Entity> for Entity {
fn to() -> RelationDef {
Relation::WatchInfo.def()
}
}
+2 -2
View File
@@ -3,8 +3,8 @@
use flix_model::id::CollectionId;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a flix collection
+2 -2
View File
@@ -4,8 +4,8 @@ use flix_model::id::MovieId;
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a flix movie
+14 -7
View File
@@ -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
+2 -2
View File
@@ -7,8 +7,8 @@ use seamantic::model::duration::Seconds;
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a tmdb episode
+2 -2
View File
@@ -3,8 +3,8 @@
use flix_model::id::{CollectionId, RawId};
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a watched movie
+2 -2
View File
@@ -5,8 +5,8 @@ use flix_model::numbers::{EpisodeNumber, SeasonNumber};
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a watched movie
+2 -4
View File
@@ -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);
+2 -2
View File
@@ -4,8 +4,8 @@ use flix_model::id::{MovieId, RawId};
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a watched movie
+2 -2
View File
@@ -5,8 +5,8 @@ use flix_model::numbers::SeasonNumber;
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a watched movie
+2 -2
View File
@@ -4,8 +4,8 @@ use flix_model::id::{RawId, ShowId};
use chrono::NaiveDate;
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
/// The database representation of a watched movie
@@ -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")
+5 -3
View File
@@ -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)
+3 -3
View File
@@ -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")
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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")
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix"
version = "0.0.9"
version = "0.0.12"
categories = []
description = "Mechanisms for interacting with flix media"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-fs"
version = "0.0.9"
version = "0.0.12"
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"] }
+13 -13
View File
@@ -47,17 +47,17 @@ pub enum Scanner {
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// 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<String>,
},
/// 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<String>,
},
@@ -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<show::Scanner> 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<generic::Scanner> 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,
},
+3 -3
View File
@@ -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<Item = Item> {
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,
}),
+17 -15
View File
@@ -51,17 +51,17 @@ pub enum Scanner {
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// 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<String>,
},
/// 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<String>,
},
@@ -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<collection::Scanner> 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<show::Scanner> 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 {
+15 -12
View File
@@ -22,10 +22,10 @@ pub type Item = crate::Item<Scanner>;
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<String>,
},
@@ -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<episode::Scanner> 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<Item = Item> {
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::<SeasonNumber>() else {
let Ok(season_number) = s_str.parse::<SeasonNumber>() 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());
}
}
+8 -8
View File
@@ -24,17 +24,17 @@ pub enum Scanner {
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// 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<String>,
},
/// 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<String>,
},
@@ -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<season::Scanner> 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,
},
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-model"
version = "0.0.9"
version = "0.0.12"
categories = []
description = "Core types for flix data"
-18
View File
@@ -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<EpisodeNumber> {
// 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(&[]),
// }
// }
// }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-tmdb"
version = "0.0.9"
version = "0.0.12"
categories = []
description = "Clients and models for fetching data from TMDB"
+5 -5
View File
@@ -150,8 +150,8 @@ mod tests {
#[cfg(feature = "sea-orm")]
fn test_sea_orm() {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::Id;
@@ -184,7 +184,7 @@ mod tests {
/// Type alias for the raw ID representation
pub use self::TmdbRepr as RawId;
/// A placeholder type used for CollectionId
#[doc(hidden)]
pub enum Collection {}
/// Type alias for a collection ID
pub type CollectionId = Id<Collection>;
@@ -203,7 +203,7 @@ impl TryFrom<flix_model::id::CollectionId> for CollectionId {
}
}
/// A placeholder type used for MovieId
#[doc(hidden)]
pub enum Movie {}
/// Type alias for a movie ID
pub type MovieId = Id<Movie>;
@@ -222,7 +222,7 @@ impl TryFrom<flix_model::id::MovieId> for MovieId {
}
}
/// A placeholder type used for ShowId
#[doc(hidden)]
pub enum Show {}
/// Type alias for a show ID
pub type ShowId = Id<Show>;