Files
flix/crates/cli/src/run/tmdb.rs
T

508 lines
13 KiB
Rust

use std::collections::HashMap;
use flix::db::entity;
use flix::model::id::{CollectionId, MovieId, ShowId};
use flix::model::numbers::{EpisodeNumber, SeasonNumber};
use flix::tmdb::Client;
use flix::tmdb::model::id::{
CollectionId as TmdbCollectionId, MovieId as TmdbMovieId, ShowId as TmdbShowId,
};
use anyhow::{Context, Result, bail};
use chrono::Utc;
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::{
ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, TransactionError, TransactionTrait,
};
use crate::cli::tmdb::Command;
pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> Result<()> {
match command {
Command::Collection { id } => {
let id = TmdbCollectionId::from_raw(id);
let collection = entity::tmdb::collections::Entity::find_by_id(id)
.one(db)
.await?;
if collection.is_some() {
bail!("collection already exists");
}
let collection = client
.collections()
.get_details(id, None)
.await
.with_context(|| format!("collections().get_details({})", id.into_raw()))?;
let result: Result<CollectionId, TransactionError<DbErr>> = db
.transaction(|txn| {
Box::pin(async move {
let flix = entity::info::collections::ActiveModel {
id: NotSet,
title: Set(collection.title),
overview: Set(collection.overview),
}
.insert(txn)
.await?;
entity::tmdb::collections::ActiveModel {
tmdb_id: Set(id),
flix_id: Set(flix.id),
last_update: Set(Utc::now().date_naive()),
movie_count: Set(collection.movies.len().try_into().unwrap_or(0)),
}
.insert(txn)
.await?;
Ok(flix.id)
})
})
.await;
let flix_id = match result {
Ok(id) => id,
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!("Created Collection: {}", flix_id.into_raw());
Ok(())
}
Command::Movie { id } => {
let id = TmdbMovieId::from_raw(id);
let movie = entity::tmdb::movies::Entity::find_by_id(id).one(db).await?;
if movie.is_some() {
bail!("movie already exists");
}
let movie = client
.movies()
.get_details(id, None)
.await
.with_context(|| format!("movies().get_details({})", id.into_raw()))?;
let result: Result<MovieId, TransactionError<DbErr>> = db
.transaction(|txn| {
Box::pin(async move {
let flix = entity::info::movies::ActiveModel {
id: NotSet,
title: Set(movie.title),
tagline: Set(movie.tagline),
overview: Set(movie.overview),
date: Set(movie.release_date),
}
.insert(txn)
.await?;
entity::tmdb::movies::ActiveModel {
tmdb_id: Set(id),
flix_id: Set(flix.id),
last_update: Set(Utc::now().date_naive()),
runtime: Set(movie.runtime.into()),
collection: Set(movie.collection.map(|c| c.id)),
}
.insert(txn)
.await?;
Ok(flix.id)
})
})
.await;
let flix_id = match result {
Ok(id) => id,
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!("Created Movie: {}", flix_id.into_raw());
Ok(())
}
Command::Show { id } => {
let id = TmdbShowId::from_raw(id);
let show = entity::tmdb::shows::Entity::find_by_id(id).one(db).await?;
if show.is_some() {
bail!("show already exists");
}
let show = client
.shows()
.get_details(id, None)
.await
.with_context(|| format!("shows().get_details({})", id.into_raw()))?;
let mut seasons = Vec::new();
let mut episodes = HashMap::new();
for season in 1..=show.number_of_seasons {
let season = client
.seasons()
.get_details(id, season, None)
.await
.with_context(|| {
format!("seasons().get_details({}, {})", id.into_raw(), season)
})?;
if season.air_date > Utc::now().date_naive() {
eprintln!(
"skipping season ({}, {})",
id.into_raw(),
season.season_number
);
break;
}
let Ok(number_of_episodes) = EpisodeNumber::try_from(season.episodes.len()) else {
bail!(
"could not convert {} to an EpisodeNumber",
season.episodes.len()
)
};
let mut season_episodes = Vec::new();
for episode in 1..=number_of_episodes {
let Ok(episode) = client
.episodes()
.get_details(id, season.season_number, episode, None)
.await
else {
eprintln!(
"skipping episode ({}, {}, {})",
id.into_raw(),
season.season_number,
episode
);
break;
};
season_episodes.push(episode);
}
episodes.insert(season.season_number, season_episodes);
seasons.push(season);
}
let result: Result<ShowId, TransactionError<DbErr>> = db
.transaction(|txn| {
Box::pin(async move {
let flix = entity::info::shows::ActiveModel {
id: NotSet,
title: Set(show.title),
tagline: Set(show.tagline),
overview: Set(show.overview),
date: Set(show.first_air_date),
}
.insert(txn)
.await?;
entity::tmdb::shows::ActiveModel {
tmdb_id: Set(id),
flix_id: Set(flix.id),
last_update: Set(Utc::now().date_naive()),
number_of_seasons: Set(show.number_of_seasons),
}
.insert(txn)
.await?;
for season in seasons {
entity::info::seasons::ActiveModel {
show: Set(flix.id),
season: Set(season.season_number),
title: Set(season.title),
overview: Set(season.overview),
date: Set(season.air_date),
}
.insert(txn)
.await?;
entity::tmdb::seasons::ActiveModel {
tmdb_show: Set(id),
tmdb_season: Set(season.season_number),
flix_show: Set(flix.id),
flix_season: Set(season.season_number),
last_update: Set(Utc::now().date_naive()),
}
.insert(txn)
.await?;
}
for (season, episodes) in episodes {
for episode in episodes {
entity::info::episodes::ActiveModel {
show: Set(flix.id),
season: Set(season),
episode: Set(episode.episode_number),
title: Set(episode.title),
overview: Set(episode.overview),
date: Set(episode.air_date),
}
.insert(txn)
.await?;
entity::tmdb::episodes::ActiveModel {
tmdb_show: Set(id),
tmdb_season: Set(season),
tmdb_episode: Set(episode.episode_number),
flix_show: Set(flix.id),
flix_season: Set(season),
flix_episode: Set(episode.episode_number),
last_update: Set(Utc::now().date_naive()),
runtime: Set(episode.runtime.into()),
}
.insert(txn)
.await?;
}
}
Ok(flix.id)
})
})
.await;
let flix_id = match result {
Ok(id) => id,
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!("Created Show: {}", flix_id.into_raw());
Ok(())
}
Command::Season { id, season } => {
let id = TmdbShowId::from_raw(id);
let season_number = season;
let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else {
bail!("show does not exists");
};
let season = entity::tmdb::seasons::Entity::find_by_id((id, season))
.one(db)
.await?;
if season.is_some() {
bail!("season already exists");
}
let season = client
.seasons()
.get_details(id, season_number, None)
.await
.with_context(|| {
format!(
"seasons().get_details({}, {})",
id.into_raw(),
season_number
)
})?;
let mut episodes = Vec::new();
let Ok(number_of_episodes) = EpisodeNumber::try_from(season.episodes.len()) else {
bail!(
"could not convert {} to an EpisodeNumber",
season.episodes.len()
)
};
for episode in 1..=number_of_episodes {
let Ok(episode) = client
.episodes()
.get_details(id, season.season_number, episode, None)
.await
else {
eprintln!(
"skipping episode ({}, {}, {})",
id.into_raw(),
season.season_number,
episode
);
break;
};
episodes.push(episode);
}
let result: Result<(), TransactionError<DbErr>> = db
.transaction(|txn| {
Box::pin(async move {
entity::info::seasons::ActiveModel {
show: Set(show.flix_id),
season: Set(season_number),
title: Set(season.title),
overview: Set(season.overview),
date: Set(season.air_date),
}
.insert(txn)
.await?;
entity::tmdb::seasons::ActiveModel {
tmdb_show: Set(show.tmdb_id),
tmdb_season: Set(season_number),
flix_show: Set(show.flix_id),
flix_season: Set(season_number),
last_update: Set(Utc::now().date_naive()),
}
.insert(txn)
.await?;
for episode in episodes {
entity::info::episodes::ActiveModel {
show: Set(show.flix_id),
season: Set(season_number),
episode: Set(episode.episode_number),
title: Set(episode.title),
overview: Set(episode.overview),
date: Set(episode.air_date),
}
.insert(txn)
.await?;
entity::tmdb::episodes::ActiveModel {
tmdb_show: Set(show.tmdb_id),
tmdb_season: Set(season_number),
tmdb_episode: Set(episode.episode_number),
flix_show: Set(show.flix_id),
flix_season: Set(season_number),
flix_episode: Set(episode.episode_number),
last_update: Set(Utc::now().date_naive()),
runtime: Set(episode.runtime.into()),
}
.insert(txn)
.await?;
}
Ok(())
})
})
.await;
match result {
Ok(_) => (),
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!(
"Created Season: {} S{}",
show.flix_id.into_raw(),
season_number
);
Ok(())
}
Command::Episode {
id,
season,
episode,
episodes,
} => {
let id = TmdbShowId::from_raw(id);
let season_number = season;
let Some(show) = entity::tmdb::shows::Entity::find_by_id(id).one(db).await? else {
bail!("show does not exists");
};
let Some(_) = entity::tmdb::seasons::Entity::find_by_id((id, season))
.one(db)
.await?
else {
bail!("season does not exists");
};
async fn fetch_episode(
client: &Client,
db: &DatabaseConnection,
flix_id: ShowId,
tmdb_id: TmdbShowId,
id: TmdbShowId,
season: SeasonNumber,
episode: EpisodeNumber,
) -> Result<()> {
let episode_number = episode;
let episode = entity::tmdb::episodes::Entity::find_by_id((id, season, episode))
.one(db)
.await?;
if episode.is_some() {
bail!("episode already exists");
}
let episode = client
.episodes()
.get_details(id, season, episode_number, None)
.await
.with_context(|| {
format!("episodes().get_details({}, {})", id.into_raw(), season)
})?;
let result: Result<(), TransactionError<DbErr>> = db
.transaction(|txn| {
Box::pin(async move {
entity::info::episodes::ActiveModel {
show: Set(flix_id),
season: Set(season),
episode: Set(episode_number),
title: Set(episode.title),
overview: Set(episode.overview),
date: Set(episode.air_date),
}
.insert(txn)
.await?;
entity::tmdb::episodes::ActiveModel {
tmdb_show: Set(tmdb_id),
tmdb_season: Set(season),
tmdb_episode: Set(episode_number),
flix_show: Set(flix_id),
flix_season: Set(season),
flix_episode: Set(episode_number),
last_update: Set(Utc::now().date_naive()),
runtime: Set(episode.runtime.into()),
}
.insert(txn)
.await?;
Ok(())
})
})
.await;
match result {
Ok(_) => (),
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!(
"Created Episode: {} S{}E{}",
flix_id.into_raw(),
season,
episode_number
);
Ok(())
}
let flix_id = show.flix_id;
let tmdb_id = show.tmdb_id;
fetch_episode(&client, db, flix_id, tmdb_id, id, season_number, episode).await?;
for episode in episodes {
fetch_episode(&client, db, flix_id, tmdb_id, id, season_number, episode).await?;
}
Ok(())
}
}
}
pub async fn update(client: Client, database: &DatabaseConnection, command: Command) -> Result<()> {
_ = client;
_ = database;
_ = command;
unimplemented!("updates")
}
pub async fn delete(client: Client, database: &DatabaseConnection, command: Command) -> Result<()> {
_ = client;
_ = database;
_ = command;
unimplemented!("deletions")
}