use std::time::{Duration, Instant}; use julid::Julid; use sqlx::{query_scalar, SqlitePool}; use crate::{util::year_to_epoch, ShowKind, User, Watch, WatchQuest}; const USER_EXISTS_QUERY: &str = "select count(*) from users where id = $1"; const MOVIE_QUERY: &str = "select * from movie_titles order by year, title asc limit ?"; //-************************************************************************ // the omega user is the system ID, but has no actual power in the app //-************************************************************************ const OMEGA_ID: Julid = Julid::omega(); const BULK_INSERT: usize = 2_000; #[derive(Debug, sqlx::FromRow, Clone)] pub struct ImportMovieOmega { pub title: String, pub year: Option, pub runtime: Option, } impl From for Watch { fn from(value: ImportMovieOmega) -> Self { Watch { id: OMEGA_ID, // this is ignored by the inserter title: value.title, release_date: year_to_epoch(value.year.as_deref()), length: value.runtime.and_then(|v| v.parse::().ok()), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID, } } } impl From<&ImportMovieOmega> for Watch { fn from(value: &ImportMovieOmega) -> Self { Watch { id: OMEGA_ID, title: value.title.to_string(), release_date: year_to_epoch(value.year.as_deref()), length: value.runtime.as_ref().and_then(|v| v.parse::().ok()), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID, } } } //-************************************************************************ // utility functions for building CLI tools, currently just for benchmarking //-************************************************************************ pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Result<(), ()> { let mut builder = sqlx::QueryBuilder::new("insert into watch_quests (user, watch) "); builder.push_values(quests, |mut b, quest| { let user = quest.user; let watch = quest.watch; //eprintln!("{user}, {watch}"); b.push_bind(user).push_bind(watch); }); let q = builder.build(); q.execute(pool).await.map_err(|e| { dbg!(e); })?; Ok(()) } pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result { let mut builder = sqlx::QueryBuilder::new("insert into users (username, displayname, email, pwhash)"); let start = Instant::now(); builder.push_values(users.iter(), |mut b, user| { b.push_bind(&user.username) .push_bind(&user.displayname) .push_bind(&user.email) .push_bind(&user.pwhash); }); let q = builder.build(); q.execute(db_pool).await.map_err(|_| ())?; let end = Instant::now(); let dur = end - start; Ok(dur) } pub async fn add_omega_watches( w2w_db: &SqlitePool, movie_db: &SqlitePool, num: u32, ) -> Result { let omega = ensure_omega(w2w_db).await; let movies: Vec = sqlx::query_as(MOVIE_QUERY) .bind(num) .fetch_all(movie_db) .await .unwrap(); let start = Instant::now(); for movies in movies.as_slice().chunks(BULK_INSERT) { let mut builder = sqlx::QueryBuilder::new( "insert into watches (kind, title, length, release_date, added_by) ", ); builder.push_values(movies, |mut b, movie| { let title = &movie.title; b.push_bind(ShowKind::Movie) .push_bind(title) .push_bind(movie.runtime.as_ref().and_then(|l| l.parse::().ok())) .push_bind(year_to_epoch(movie.year.as_deref())) .push_bind(omega); }); let q = builder.build(); q.execute(w2w_db).await.map_err(|_| ())?; } let end = Instant::now(); let dur = end - start; Ok(dur) } pub async fn ensure_omega(db_pool: &SqlitePool) -> Julid { if !check_omega_exists(db_pool).await { sqlx::query("insert into users (id, username, pwhash) values (?, 'the omega user', 'you shall not password')").bind(OMEGA_ID).execute(db_pool).await.unwrap(); } OMEGA_ID } async fn check_omega_exists(db_pool: &SqlitePool) -> bool { let count = query_scalar(USER_EXISTS_QUERY) .bind(OMEGA_ID) .fetch_one(db_pool) .await .unwrap_or(0); count > 0 } //-************************************************************************ //tests //-************************************************************************ #[cfg(test)] mod test { use tokio::runtime::Runtime; use super::*; #[test] fn ensure_omega_user() { let p = crate::db::get_db_pool(); let rt = Runtime::new().unwrap(); rt.block_on(async { assert!(!check_omega_exists(&p).await); ensure_omega(&p).await; }); assert!(rt.block_on(check_omega_exists(&p))); } }