use sqlx::{query, query_scalar, SqlitePool}; use tokio::task::JoinSet; use tokio_retry::Retry; use crate::{ db_id::DbId, util::year_to_epoch, watches::handlers::add_new_watch_impl, ShowKind, Watch, WatchQuest, }; const USER_EXISTS_QUERY: &str = "select count(*) from witches where id = $1"; //-************************************************************************ // the omega user is the system ID, but has no actual power in the app //-************************************************************************ const OMEGA_ID: u128 = u128::MAX; #[derive(Debug, sqlx::FromRow, Clone)] pub struct ImportMovieOmega { pub title: String, pub year: Option, pub length: Option, } impl From for Watch { fn from(value: ImportMovieOmega) -> Self { Watch { title: value.title, release_date: year_to_epoch(value.year.as_deref()), length: value.length.and_then(|v| v.parse::().ok()), id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID.into(), } } } impl From<&ImportMovieOmega> for Watch { fn from(value: &ImportMovieOmega) -> Self { Watch { title: value.title.to_string(), release_date: year_to_epoch(value.year.as_deref()), length: value.length.as_ref().and_then(|v| v.parse::().ok()), id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID.into(), } } } //-************************************************************************ // utility functions for building CLI tools, currently just for benchmarking //-************************************************************************ pub async fn add_watch_omega(db_pool: &SqlitePool, movie: &ImportMovieOmega) -> Result<(), ()> { let watch: Watch = movie.into(); if add_new_watch_impl(db_pool, &watch, None).await.is_ok() { println!("{}", watch.id); Ok(()) } else { eprintln!("failed to add \"{}\"", watch.title); Err(()) } } pub async fn add_watch_user( db_pool: &SqlitePool, watch: &Watch, quest: WatchQuest, ) -> Result<(), ()> { if add_new_watch_impl(db_pool, watch, Some(quest)) .await .is_ok() { println!("{}", watch.id); Ok(()) } else { eprintln!("failed to add \"{}\"", watch.title); Err(()) } } pub async fn add_user( db_pool: &SqlitePool, username: &str, displayname: Option<&str>, email: Option<&str>, id: Option, ) { let pwhash = "you shall not password"; let id: DbId = id.unwrap_or_else(DbId::new); if query(crate::signup::CREATE_QUERY) .bind(id) .bind(username) .bind(displayname) .bind(email) .bind(pwhash) .execute(db_pool) .await .is_ok() { println!("{id}"); } else { eprintln!("failed to add user \"{username}\""); } } pub async fn add_omega_watches(ww_db: &SqlitePool, movies: Vec) { ensure_omega(ww_db).await; let mut set = JoinSet::new(); let retry_strategy = tokio_retry::strategy::ExponentialBackoff::from_millis(100) .map(tokio_retry::strategy::jitter) .take(4); for movie in movies { let db = ww_db.clone(); let title = movie.title.as_str(); let year = movie.year.clone().unwrap(); let len = movie.length.clone().unwrap(); let retry_strategy = retry_strategy.clone(); let key = format!("{title}{year}{len}"); set.spawn(async move { ( key, Retry::spawn(retry_strategy, || async { add_watch_omega(&db, &movie).await }) .await, ) }); } // stragglers while (set.join_next().await).is_some() {} } pub async fn ensure_omega(db_pool: &SqlitePool) -> DbId { if !check_omega_exists(db_pool).await { add_user( db_pool, "The Omega User", Some("I am the end of all watches."), None, Some(OMEGA_ID.into()), ) .await } OMEGA_ID.into() } async fn check_omega_exists(db_pool: &SqlitePool) -> bool { let id: DbId = OMEGA_ID.into(); let count = query_scalar(USER_EXISTS_QUERY) .bind(id) .fetch_one(db_pool) .await .unwrap_or(0); count > 0 } //-************************************************************************ //tests //-************************************************************************ #[cfg(test)] mod test { use super::*; #[tokio::test] async fn ensure_omega_user() { let p = crate::db::get_db_pool().await; assert!(!check_omega_exists(&p).await); ensure_omega(&p).await; assert!(check_omega_exists(&p).await); } }