Update all libraries to the new database format

This commit is contained in:
2026-01-21 22:05:50 -07:00
parent 0e2a8e425b
commit 66168c120f
33 changed files with 1025 additions and 794 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-cli"
version = "0.0.16"
version = "0.0.17"
edition.workspace = true
rust-version.workspace = true
description = "CLI for interacting with a flix database"
+19 -1
View File
@@ -1,12 +1,30 @@
use flix::model::numbers::{EpisodeNumber, SeasonNumber};
use chrono::NaiveDate;
use clap::Subcommand;
#[derive(Subcommand)]
pub enum AddCommand {
/// Process a flix collection
/// Add a flix collection
Collection {
#[arg(value_name = "TITLE")]
title: String,
#[arg(value_name = "OVERVIEW")]
overview: String,
},
/// Add a flix episode
Episode {
#[arg(value_name = "SHOW_WEB_SLUG")]
show_slug: String,
#[arg(value_name = "NUMBER")]
season_number: SeasonNumber,
#[arg(value_name = "NUMBER")]
episode_number: EpisodeNumber,
#[arg(value_name = "TITLE")]
title: String,
#[arg(value_name = "OVERVIEW")]
overview: String,
#[arg(value_name = "DATE")]
air_date: NaiveDate,
},
}
+25 -3
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use anyhow::{Result, anyhow};
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};
pub mod flix;
pub mod tmdb;
@@ -13,8 +13,12 @@ pub struct Cli {
#[arg(short, long, value_name = "FILE", default_value = "~/.flix")]
config: PathBuf,
/// Use a custom cache file
#[arg(short = 'C', long, value_name = "FILE", default_value = "./flix.redb")]
cache: PathBuf,
/// Use a custom database file
#[arg(short, long, value_name = "DATABASE", default_value = "./flix.db")]
#[arg(short, long, value_name = "FILE", default_value = "./flix.db")]
database: PathBuf,
/// Enable tracing
@@ -38,6 +42,10 @@ impl Cli {
}
}
pub fn cache_path(&self) -> &Path {
&self.cache
}
pub fn database_path(&self) -> Result<String> {
self.database
.as_os_str()
@@ -51,12 +59,26 @@ impl Cli {
}
}
#[derive(Args)]
pub struct AddOverrides {
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub sort_title: Option<String>,
#[arg(long)]
pub fs_slug: Option<String>,
#[arg(long)]
pub web_slug: Option<String>,
}
#[derive(Subcommand)]
pub enum Command {
/// Initialize a new database
Init,
/// Add new items to the database
Add {
#[command(flatten)]
overrides: AddOverrides,
#[command(subcommand)]
command: AddCommand,
},
+20 -6
View File
@@ -1,6 +1,7 @@
use std::path::PathBuf;
use std::rc::Rc;
use flix::tmdb::Client;
use flix::tmdb::{self, CachePolicy, Client, RedbCache};
use anyhow::{Context, Result};
use clap::Parser;
@@ -12,6 +13,8 @@ use cli::{AddCommand, Cli, Command, DeleteCommand, UpdateCommand};
mod config;
use config::Config;
use crate::cli::AddOverrides;
mod db;
mod run;
@@ -26,7 +29,11 @@ async fn main() -> Result<()> {
let database_path = cli.database_path()?;
let client = Client::new(config.tmdb().bearer_token().to_owned());
let client = Client::new(
tmdb::Config::new(config.tmdb().bearer_token().to_owned()),
Rc::new(RedbCache::new(cli.cache_path())?),
CachePolicy::Full,
);
if cli.trace {
tracing_subscriber::fmt()
@@ -37,7 +44,9 @@ async fn main() -> Result<()> {
match cli.command() {
Command::Init => exec_init(database_path).await?,
Command::Add { command } => exec_add(client, database_path, command).await?,
Command::Add { command, overrides } => {
exec_add(client, database_path, command, overrides).await?
}
Command::Update { command } => exec_update(client, database_path, command).await?,
Command::Delete { command } => exec_delete(client, database_path, command).await?,
Command::Backup { output } => exec_backup(database_path, output).await?,
@@ -53,15 +62,20 @@ async fn exec_init(database_path: String) -> Result<()> {
Ok(())
}
async fn exec_add(client: Client, database_path: String, command: AddCommand) -> Result<()> {
async fn exec_add(
client: Client,
database_path: String,
command: AddCommand,
overrides: AddOverrides,
) -> Result<()> {
let database = db::open(database_path).await?;
match command {
AddCommand::Flix { command } => {
run::flix::add(database.as_ref(), command).await?;
run::flix::add(database.as_ref(), command, overrides).await?;
}
AddCommand::Tmdb { command } => {
run::tmdb::add(client, database.as_ref(), command).await?;
run::tmdb::add(client, database.as_ref(), command, overrides).await?;
}
}
+69 -6
View File
@@ -1,23 +1,35 @@
use flix::db::entity;
use flix::model::id::CollectionId;
use flix::model::id::{CollectionId, ShowId};
use flix::model::numbers::{EpisodeNumber, SeasonNumber};
use flix::model::text;
use anyhow::Result;
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, TransactionError, TransactionTrait};
use crate::cli::AddOverrides;
use crate::cli::flix::AddCommand;
pub async fn add(db: &DatabaseConnection, command: AddCommand) -> Result<()> {
pub async fn add(
db: &DatabaseConnection,
command: AddCommand,
overrides: AddOverrides,
) -> Result<()> {
match command {
AddCommand::Collection { title, overview } => {
let result: Result<CollectionId, TransactionError<DbErr>> = db
.transaction(|txn| {
let title = title.clone();
let title = overrides.title.unwrap_or_else(|| title.clone());
let sort_title = text::make_sortable_title(&title);
let fs_slug = text::make_fs_slug(&title);
let web_slug = text::make_web_slug(&title);
let sort_title = overrides
.sort_title
.unwrap_or_else(|| text::make_sortable_title(&title));
let fs_slug = overrides
.fs_slug
.unwrap_or_else(|| text::make_fs_slug(&title));
let web_slug = overrides
.web_slug
.unwrap_or_else(|| text::make_web_slug(&title));
Box::pin(async move {
let flix = entity::info::collections::ActiveModel {
@@ -43,6 +55,57 @@ pub async fn add(db: &DatabaseConnection, command: AddCommand) -> Result<()> {
};
println!("Created Collection: {} [{}]", title, flix_id.into_raw());
Ok(())
}
AddCommand::Episode {
show_slug,
season_number,
episode_number,
title,
overview,
air_date,
} => {
let result: Result<(ShowId, SeasonNumber, EpisodeNumber), TransactionError<DbErr>> = db
.transaction(|txn| {
let title = overrides.title.unwrap_or_else(|| title.clone());
Box::pin(async move {
let show = entity::info::shows::Entity::find_by_web_slug(&show_slug)
.one(txn)
.await?
.ok_or_else(|| {
DbErr::Custom(format!("show '{}' does not exist", show_slug))
})?;
let flix = entity::info::episodes::ActiveModel {
show_id: Set(show.id),
season_number: Set(season_number),
episode_number: Set(episode_number),
title: Set(title),
overview: Set(overview),
date: Set(air_date),
}
.insert(txn)
.await?;
Ok((flix.show_id, flix.season_number, flix.episode_number))
})
})
.await;
let (flix_show, season_number, episode_number) = match result {
Ok(id) => id,
Err(TransactionError::Connection(err)) => Err(err)?,
Err(TransactionError::Transaction(err)) => Err(err)?,
};
println!(
"Created Episode: {} [{} S{} E{}]",
title,
flix_show.into_raw(),
season_number,
episode_number
);
Ok(())
}
}
+45 -18
View File
@@ -16,9 +16,15 @@ use sea_orm::{
ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, TransactionError, TransactionTrait,
};
use crate::cli::AddOverrides;
use crate::cli::tmdb::Command;
pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> Result<()> {
pub async fn add(
client: Client,
db: &DatabaseConnection,
command: Command,
overrides: AddOverrides,
) -> Result<()> {
match command {
Command::Collection { id } => {
let id = TmdbCollectionId::from_raw(id);
@@ -36,18 +42,25 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
.await
.with_context(|| format!("collections().get_details({})", id.into_raw()))?;
let title = collection.title.clone();
let title = overrides.title.unwrap_or(collection.title);
let sort_title = text::make_sortable_title(&title);
let fs_slug = text::make_fs_slug(&title);
let web_slug = text::make_web_slug(&title);
let sort_title = overrides
.sort_title
.unwrap_or_else(|| text::make_sortable_title(&title));
let fs_slug = overrides
.fs_slug
.unwrap_or_else(|| text::make_fs_slug(&title));
let web_slug = overrides
.web_slug
.unwrap_or_else(|| text::make_web_slug(&title));
let result: Result<CollectionId, TransactionError<DbErr>> = db
.transaction(|txn| {
let title = title.clone();
Box::pin(async move {
let flix = entity::info::collections::ActiveModel {
id: NotSet,
title: Set(collection.title),
title: Set(title),
overview: Set(collection.overview),
sort_title: Set(sort_title),
fs_slug: Set(fs_slug),
@@ -93,19 +106,26 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
.await
.with_context(|| format!("movies().get_details({})", id.into_raw()))?;
let title = movie.title.clone();
let title = overrides.title.unwrap_or(movie.title);
let year = movie.release_date.year();
let sort_title = text::make_sortable_title(&title);
let fs_slug = text::make_fs_slug_year(&title, year);
let web_slug = text::make_web_slug_year(&title, year);
let sort_title = overrides
.sort_title
.unwrap_or_else(|| text::make_sortable_title(&title));
let fs_slug = overrides
.fs_slug
.unwrap_or_else(|| text::make_fs_slug_year(&title, year));
let web_slug = overrides
.web_slug
.unwrap_or_else(|| text::make_web_slug_year(&title, year));
let result: Result<MovieId, TransactionError<DbErr>> = db
.transaction(|txn| {
let title = title.clone();
Box::pin(async move {
let flix = entity::info::movies::ActiveModel {
id: NotSet,
title: Set(movie.title),
title: Set(title),
tagline: Set(movie.tagline),
overview: Set(movie.overview),
date: Set(movie.release_date),
@@ -161,9 +181,6 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
let mut seasons = Vec::new();
let mut episodes = HashMap::new();
let title = show.title.clone();
let year = show.first_air_date.year();
for season in 1..=show.number_of_seasons {
let season = SeasonNumber::new(season);
let season = match client
@@ -218,16 +235,26 @@ pub async fn add(client: Client, db: &DatabaseConnection, command: Command) -> R
seasons.push(season);
}
let sort_title = text::make_sortable_title(&show.title);
let fs_slug = text::make_fs_slug_year(&show.title, show.first_air_date.year());
let web_slug = text::make_web_slug_year(&show.title, show.first_air_date.year());
let title = overrides.title.unwrap_or(show.title);
let year = show.first_air_date.year();
let sort_title = overrides
.sort_title
.unwrap_or_else(|| text::make_sortable_title(&title));
let fs_slug = overrides
.fs_slug
.unwrap_or_else(|| text::make_fs_slug_year(&title, year));
let web_slug = overrides
.web_slug
.unwrap_or_else(|| text::make_web_slug_year(&title, year));
let result: Result<ShowId, TransactionError<DbErr>> = db
.transaction(|txn| {
let title = title.clone();
Box::pin(async move {
let flix = entity::info::shows::ActiveModel {
id: NotSet,
title: Set(show.title),
title: Set(title),
tagline: Set(show.tagline),
overview: Set(show.overview),
date: Set(show.first_air_date),