use std::sync::{Arc, Mutex}; 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, add_watch_quest_impl}, ShowKind, Watch, WatchQuest, }; const USER_EXISTS_QUERY: &str = "select count(*) from witches where id = $1"; const MOVIE_QUERY: &str = "select * from movies order by random() limit 10000"; //-************************************************************************ // 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() { Ok(watch.id) } else { eprintln!("failed to add \"{}\"", watch.title); Err(()) } } pub async fn add_watch_quest(db_pool: &SqlitePool, quest: WatchQuest) -> Result<(), ()> { if add_watch_quest_impl(db_pool, &quest).await.is_ok() { Ok(()) } else { eprintln!("failed to add {}", quest.id); 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, movie_db: &SqlitePool) -> Vec { ensure_omega(ww_db).await; let movies: Vec = sqlx::query_as(MOVIE_QUERY) .fetch_all(movie_db) .await .unwrap(); let mut set = JoinSet::new(); let movie_set = Vec::with_capacity(10_000); let movie_set = Arc::new(Mutex::new(movie_set)); 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 movie_set = movie_set.clone(); let key = format!("{title}{year}{len}"); set.spawn(async move { ( key, Retry::spawn(retry_strategy, || async { if let Ok(id) = add_watch_omega(&db, &movie).await { let mut mset = movie_set.lock().unwrap(); mset.push(id); Ok(()) } else { Err(()) } }) .await, ) }); } // stragglers while (set.join_next().await).is_some() {} let movies = movie_set.lock().unwrap().clone(); movies } 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); } }