Throw away flix files in favor of a flix database

This commit is contained in:
2025-09-18 22:41:34 -06:00
parent ba9c3fa03d
commit 06110b91a1
117 changed files with 8645 additions and 1054 deletions
+3 -1
View File
@@ -1,4 +1,4 @@
use super::{CollectionId, MovieId};
use super::id::{CollectionId, MovieId};
/// A deserialized Collection from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
@@ -20,4 +20,6 @@ pub struct Collection {
pub struct Item {
/// The movie's TMDB ID
pub id: MovieId,
/// The movie's title
pub title: String,
}
+10 -1
View File
@@ -1,10 +1,16 @@
use core::time::Duration;
use flix_model::numbers::EpisodeNumber;
use chrono::NaiveDate;
use super::duration_from_minutes;
/// A deserialized Episode from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Episode {
/// The episode's number
pub episode_number: u32,
pub episode_number: EpisodeNumber,
/// The episode's title
#[serde(rename = "name")]
pub title: String,
@@ -12,4 +18,7 @@ pub struct Episode {
pub overview: String,
/// The episode's air date
pub air_date: NaiveDate,
/// The movie's runtime
#[serde(deserialize_with = "duration_from_minutes")]
pub runtime: Duration,
}
-19
View File
@@ -1,19 +0,0 @@
use super::id::{MovieGenreId, ShowGenreId};
/// A deserialized movie Genre from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
pub struct MovieGenre {
/// The genre's TMDB ID
pub id: MovieGenreId,
/// The genre's name
pub name: String,
}
/// A deserialized show Genre from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ShowGenre {
/// The genre's TMDB ID
pub id: ShowGenreId,
/// The genre's name
pub name: String,
}
+215 -63
View File
@@ -1,90 +1,242 @@
//! Typed TMDB IDs
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
/// The TMDB ID type of a movie genre
pub type MovieGenreId = TmdbId<MovieGenre>;
/// The TMDB ID type of a show genre
pub type ShowGenreId = TmdbId<ShowGenre>;
/// The TMDB ID type of a collection
pub type CollectionId = TmdbId<Collection>;
/// The TMDB ID type of a movie
pub type MovieId = TmdbId<Movie>;
/// The TMDB ID type of a show
pub type ShowId = TmdbId<Show>;
#[cfg(feature = "sea-orm")]
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
#[cfg(feature = "sea-orm")]
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
pub enum MovieGenre {}
pub enum ShowGenre {}
pub enum Collection {}
pub enum Movie {}
pub enum Show {}
/// The internal representation used by TMDB
pub type TmdbRepr = u32;
/// The inner type of TmdbId
pub type Inner = u32;
/// Wraps an ID from TMDB, the generic parameter is to enforce that
/// IDs for different types of media are not interchangeable
/// An opaque type representing a TMDB ID
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
#[repr(transparent)]
pub struct TmdbId<T> {
inner: Inner,
pub struct Id<T> {
id: TmdbRepr,
#[serde(skip_serializing, default)]
_phantom: PhantomData<T>,
}
impl<T> TmdbId<T> {
/// Extract the inner value
pub fn inner(self) -> Inner {
self.inner
}
}
impl<T> From<Inner> for TmdbId<T> {
fn from(value: Inner) -> Self {
Self {
inner: value,
_phantom: PhantomData,
}
}
}
impl<T> From<TmdbId<T>> for Inner {
fn from(value: TmdbId<T>) -> Self {
value.inner
}
}
impl<T> fmt::Debug for TmdbId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> fmt::Display for TmdbId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Clone for TmdbId<T> {
// Manual implementation since `T: Clone` is not required
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TmdbId<T> {}
// Manual implementation since `T: Copy` is not required
impl<T> Copy for Id<T> {}
impl<T> PartialEq for TmdbId<T> {
// Manual implementation since `T: PartialEq` is not required
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
self.id == other.id
}
}
impl<T> Eq for TmdbId<T> {}
// Manual implementation since `T: Eq` is not required
impl<T> Eq for Id<T> {}
impl<T> Hash for TmdbId<T> {
// Manual implementation since `T: PartialOrd` is not required
impl<T> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// Manual implementation since `T: Ord` is not required
impl<T> Ord for Id<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
// Manual implementation since `T: Hash` is not required
impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
self.id.hash(state);
}
}
impl<T> Id<T> {
/// Allows the conversion from a raw value to [Id], though the use is discouraged.
pub fn from_raw(raw: TmdbRepr) -> Self {
Self {
id: raw,
_phantom: PhantomData,
}
}
/// Allows extracting the raw value, though the use is discouraged.
pub fn into_raw(self) -> TmdbRepr {
self.id
}
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Id")
.field("T", &core::any::type_name::<T>())
.field("id", &self.id)
.finish()
}
}
#[cfg(feature = "sea-orm")]
impl<T> ValueType for Id<T> {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<TmdbRepr as ValueType>::try_from(v).map(|id| Self {
id,
_phantom: PhantomData,
})
}
fn type_name() -> String {
format!("Id<{}>", &core::any::type_name::<T>())
}
fn array_type() -> ArrayType {
TmdbRepr::array_type()
}
fn column_type() -> ColumnType {
TmdbRepr::column_type()
}
}
#[cfg(feature = "sea-orm")]
impl<T> From<Id<T>> for Value {
fn from(value: Id<T>) -> Self {
value.id.into()
}
}
#[cfg(feature = "sea-orm")]
impl<T> TryGetable for Id<T> {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
TmdbRepr::try_get_by(res, index).map(|id| Self {
id,
_phantom: PhantomData,
})
}
}
#[cfg(feature = "sea-orm")]
impl<T> TryFromU64 for Id<T> {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
TmdbRepr::try_from_u64(n).map(|id| Self {
id,
_phantom: PhantomData,
})
}
}
#[cfg(feature = "sea-orm")]
impl<T> Nullable for Id<T> {
fn null() -> Value {
TmdbRepr::null()
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "sea-orm")]
fn test_sea_orm() {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
};
use super::Id;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ids")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>,
nullable: Option<Id<Model>>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
#[test]
fn test_serde() {
use super::Id;
let id: Id<()> = Id::from_raw(1234);
serde_test::assert_tokens(&id, &[serde_test::Token::U32(1234)]);
}
}
/// Type alias for the raw ID representation
pub use self::TmdbRepr as RawId;
/// A placeholder type used for CollectionId
pub enum Collection {}
/// Type alias for a collection ID
pub type CollectionId = Id<Collection>;
impl From<CollectionId> for flix_model::id::CollectionId {
fn from(value: CollectionId) -> Self {
Self::from_raw(value.into_raw().into())
}
}
impl TryFrom<flix_model::id::CollectionId> for CollectionId {
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
fn try_from(value: flix_model::id::CollectionId) -> Result<Self, Self::Error> {
value.into_raw().try_into().map(Self::from_raw)
}
}
/// A placeholder type used for MovieId
pub enum Movie {}
/// Type alias for a movie ID
pub type MovieId = Id<Movie>;
impl From<MovieId> for flix_model::id::MovieId {
fn from(value: MovieId) -> Self {
Self::from_raw(value.into_raw().into())
}
}
impl TryFrom<flix_model::id::MovieId> for MovieId {
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
fn try_from(value: flix_model::id::MovieId) -> Result<Self, Self::Error> {
value.into_raw().try_into().map(Self::from_raw)
}
}
/// A placeholder type used for ShowId
pub enum Show {}
/// Type alias for a show ID
pub type ShowId = Id<Show>;
impl From<ShowId> for flix_model::id::ShowId {
fn from(value: ShowId) -> Self {
Self::from_raw(value.into_raw().into())
}
}
impl TryFrom<flix_model::id::ShowId> for ShowId {
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
fn try_from(value: flix_model::id::ShowId) -> Result<Self, Self::Error> {
value.into_raw().try_into().map(Self::from_raw)
}
}
+15 -6
View File
@@ -1,18 +1,27 @@
//! Deserializable types from the TMDB API
use core::time::Duration;
use serde::{Deserialize, Deserializer};
pub mod id;
mod collection;
mod episode;
mod genre;
mod id;
mod movie;
mod season;
mod show;
pub use collection::*;
pub use episode::*;
pub use genre::*;
pub use movie::*;
pub use season::*;
pub use serde::*;
pub use show::*;
pub use id::{CollectionId, MovieGenreId, MovieId, ShowGenreId, ShowId};
pub use id::{Inner as TmdbIdInner, TmdbId};
fn duration_from_minutes<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let minutes = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(minutes.saturating_mul(60)))
}
+19 -27
View File
@@ -1,6 +1,9 @@
use core::time::Duration;
use chrono::NaiveDate;
use super::{CollectionId, MovieGenre, MovieId};
use super::duration_from_minutes;
use super::id::{CollectionId, MovieId};
/// A deserialized Movie from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
@@ -12,14 +15,15 @@ pub struct Movie {
pub collection: Option<InCollection>,
/// The movie's title
pub title: String,
/// The movie's tagline
pub tagline: String,
/// The movie's overview
pub overview: String,
/// The list of genres the movie belongs to
pub genres: Vec<MovieGenre>,
/// The movie's release date
pub release_date: NaiveDate,
/// The movie's status
pub status: MovieStatus,
/// The movie's runtime
#[serde(deserialize_with = "duration_from_minutes")]
pub runtime: Duration,
}
/// A deserialized movie's collection from the TMDB API
@@ -27,27 +31,15 @@ pub struct Movie {
pub struct InCollection {
/// The collection's TMDB ID
pub id: CollectionId,
/// The collection's title
#[serde(rename = "name")]
pub title: String,
}
/// A deserialized movie status from the TMDB API
#[derive(Debug, Clone, Copy, serde::Deserialize)]
pub enum MovieStatus {
/// The movie was cancelled
#[serde(rename = "Canceled")]
Canceled,
/// The movie is in production
#[serde(rename = "In Production")]
InProduction,
/// The movie is planned
#[serde(rename = "Planned")]
Planned,
/// The movie is in post production
#[serde(rename = "Post Production")]
PostProduction,
/// The movie is released
#[serde(rename = "Released")]
Released,
/// The movie is rumored
#[serde(rename = "Rumored")]
Rumored,
}
// TODO: Genres
// pub genres: Vec<Genre>,
// where: struct Genre { id, name }
// TODO: Company
// pub companies: Vec<Company>
// where: struct Company { id, name }
+8 -4
View File
@@ -1,12 +1,12 @@
use chrono::NaiveDate;
use super::Episode;
use flix_model::numbers::SeasonNumber;
/// A deserialized Season from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Season {
/// The season's number
pub season_number: u32,
pub season_number: SeasonNumber,
/// The season's title
#[serde(rename = "name")]
pub title: String,
@@ -14,6 +14,10 @@ pub struct Season {
pub overview: String,
/// The season's air date
pub air_date: NaiveDate,
/// The list of episodes in this season
pub episodes: Vec<Episode>,
/// The number of episodes in this season
pub episodes: Vec<FakeEpisode>,
}
/// A placeholder struct for parsing the episodes list for a season
#[derive(Debug, Clone, serde::Deserialize)]
pub struct FakeEpisode {}
+16 -27
View File
@@ -1,6 +1,6 @@
use chrono::NaiveDate;
use super::{ShowGenre, ShowId};
use super::id::ShowId;
/// A deserialized Show from the TMDB API
#[derive(Debug, Clone, serde::Deserialize)]
@@ -10,39 +10,28 @@ pub struct Show {
/// The show's title
#[serde(rename = "name")]
pub title: String,
/// The show's tagline
pub tagline: String,
/// The show's overview
pub overview: String,
/// The list of genres this show belongs to
pub genres: Vec<ShowGenre>,
/// The show's first air date
pub first_air_date: NaiveDate,
/// The show's last air date
pub last_air_date: NaiveDate,
/// The total number of episodes in this show
pub number_of_episodes: u32,
/// The number of seasons in this show
pub number_of_seasons: u32,
/// The show's status
pub status: ShowStatus,
}
/// A deserialized show Status from the TMDB API
#[derive(Debug, Clone, Copy, serde::Deserialize)]
pub enum ShowStatus {
/// The show is returning
#[serde(rename = "Returning Series")]
Returning,
/// The show is planned
#[serde(rename = "Planned")]
Planned,
/// The show is in procuction
#[serde(rename = "In Production")]
InProduction,
/// The show has ended
#[serde(rename = "Ended")]
Ended,
/// The show is canceled
#[serde(rename = "Canceled")]
Canceled,
/// The show only released a pilot
#[serde(rename = "Pilot")]
Pilot,
}
// TODO: Genres
// pub genres: Vec<Genre>,
// where: struct Genre { id, name }
// TODO: Network
// pub networks: Vec<Network>
// where: struct Network { id, name }
// TODO: Company
// pub companies: Vec<Company>
// where: struct Company { id, name }