// yt - A fully featured command line YouTube client // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see . //! These functions interact with the storage db in a read-only way. They are added on-demaned (as //! you could theoretically just could do everything with the `get_videos` function), as //! performance or convince requires. use std::{fs::File, path::PathBuf}; use anyhow::{bail, Context, Result}; use blake3::Hash; use log::debug; use sqlx::{query, QueryBuilder, Row, Sqlite}; use url::Url; use yt_dlp::wrapper::info_json::InfoJson; use crate::{ app::App, storage::{ subscriptions::Subscription, video_database::{extractor_hash::ExtractorHash, Video}, }, }; use super::{MpvOptions, VideoOptions, VideoStatus, YtDlpOptions}; macro_rules! video_from_record { ($record:expr) => { let thumbnail_url = if let Some(url) = &$record.thumbnail_url { Some(Url::parse(&url).expect("Parsing this as url should always work")) } else { None }; Ok(Video { cache_path: $record.cache_path.as_ref().map(|val| PathBuf::from(val)), description: $record.description.clone(), duration: $record.duration, extractor_hash: ExtractorHash::from_hash( $record .extractor_hash .parse() .expect("The db hash should be a valid blake3 hash"), ), last_status_change: $record.last_status_change, parent_subscription_name: $record.parent_subscription_name.clone(), publish_date: $record.publish_date, status: VideoStatus::from_db_integer($record.status), thumbnail_url, title: $record.title.clone(), url: Url::parse(&$record.url).expect("Parsing this as url should always work"), priority: $record.priority, status_change: if $record.status_change == 1 { true } else { assert_eq!($record.status_change, 0); false }, }) }; } /// Get the lines to display at the selection file /// [`changing` = true]: Means that we include *only* videos, that have the `status_changing` flag set /// [`changing` = None]: Means that we include *both* videos, that have the `status_changing` flag set and not set pub async fn get_videos( app: &App, allowed_states: &[VideoStatus], changing: Option, ) -> Result> { let mut qb: QueryBuilder = QueryBuilder::new( "\ SELECT * FROM videos WHERE status IN ", ); qb.push("("); allowed_states .iter() .enumerate() .for_each(|(index, state)| { qb.push("'"); qb.push(state.as_db_integer()); qb.push("'"); if index != allowed_states.len() - 1 { qb.push(","); } }); qb.push(")"); if let Some(val) = changing { if val { qb.push(" AND status_change = 1"); } else { qb.push(" AND status_change = 0"); } } qb.push("\n ORDER BY priority DESC, publish_date DESC;"); debug!("Will run: \"{}\"", qb.sql()); let videos = qb.build().fetch_all(&app.database).await.with_context(|| { format!( "Failed to query videos with states: '{}'", allowed_states.iter().fold(String::new(), |mut acc, state| { acc.push(' '); acc.push_str(state.as_str()); acc }), ) })?; let real_videos: Vec