From 9496583cc76fbd7347384716f2898f870743f16d Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Sat, 22 Feb 2025 11:44:13 +0100 Subject: refactor(yt/storage/video_database): Move `getters,setters` to `get,set` This also removes some `get_`/`set_` prefixes from the functions in these modules, as `get::` is more idiomatic than `get_`. --- yt/src/storage/video_database/get/mod.rs | 302 ++++++++++++++++++ .../video_database/get/playlist/iterator.rs | 91 ++++++ yt/src/storage/video_database/get/playlist/mod.rs | 157 ++++++++++ yt/src/storage/video_database/set/mod.rs | 340 +++++++++++++++++++++ yt/src/storage/video_database/set/playlist.rs | 71 +++++ 5 files changed, 961 insertions(+) create mode 100644 yt/src/storage/video_database/get/mod.rs create mode 100644 yt/src/storage/video_database/get/playlist/iterator.rs create mode 100644 yt/src/storage/video_database/get/playlist/mod.rs create mode 100644 yt/src/storage/video_database/set/mod.rs create mode 100644 yt/src/storage/video_database/set/playlist.rs diff --git a/yt/src/storage/video_database/get/mod.rs b/yt/src/storage/video_database/get/mod.rs new file mode 100644 index 0000000..8fe0754 --- /dev/null +++ b/yt/src/storage/video_database/get/mod.rs @@ -0,0 +1,302 @@ +// 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-demand (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::{Context, Result, bail}; +use blake3::Hash; +use log::{debug, trace}; +use sqlx::query; +use yt_dlp::wrapper::info_json::InfoJson; + +use crate::{ + app::App, + storage::{ + subscriptions::Subscription, + video_database::{Video, extractor_hash::ExtractorHash}, + }, + unreachable::Unreachable, +}; + +use super::{MpvOptions, VideoOptions, VideoStatus, VideoStatusMarker, YtDlpOptions}; + +mod playlist; +pub use playlist::*; + +#[macro_export] +macro_rules! video_from_record { + ($record:expr) => { + Video { + description: $record.description.clone(), + duration: $crate::storage::video_database::MaybeDuration::from_maybe_secs_f64( + $record.duration, + ), + extractor_hash: + $crate::storage::video_database::extractor_hash::ExtractorHash::from_hash( + $record + .extractor_hash + .parse() + .expect("The db hash should be a valid blake3 hash"), + ), + last_status_change: $crate::storage::video_database::TimeStamp::from_secs( + $record.last_status_change, + ), + parent_subscription_name: $record.parent_subscription_name.clone(), + publish_date: $record + .publish_date + .map(|pd| $crate::storage::video_database::TimeStamp::from_secs(pd)), + status: { + let marker = $crate::storage::video_database::VideoStatusMarker::from_db_integer( + $record.status, + ); + + let optional = if let Some(cache_path) = &$record.cache_path { + Some(( + PathBuf::from(cache_path), + if $record.is_focused == 1 { true } else { false }, + )) + } else { + None + }; + + $crate::storage::video_database::VideoStatus::from_marker(marker, optional) + }, + thumbnail_url: if let Some(url) = &$record.thumbnail_url { + Some(url::Url::parse(&url).expect("Parsing this as url should always work")) + } else { + None + }, + title: $record.title.clone(), + url: url::Url::parse(&$record.url).expect("Parsing this as url should always work"), + priority: $crate::storage::video_database::Priority::from($record.priority), + + watch_progress: std::time::Duration::from_secs( + u64::try_from($record.watch_progress).expect("The record is positive i64"), + ), + } + }; +} + +/// Returns the videos that are in the `allowed_states`. +/// +/// # Panics +/// Only, if assertions fail. +pub async fn videos(app: &App, allowed_states: &[VideoStatusMarker]) -> Result> { + fn test(all_states: &[VideoStatusMarker], check: VideoStatusMarker) -> Option { + if all_states.contains(&check) { + trace!("State '{check:?}' marked as active"); + Some(check.as_db_integer()) + } else { + trace!("State '{check:?}' marked as inactive"); + None + } + } + fn states_to_string(allowed_states: &[VideoStatusMarker]) -> String { + let mut states = allowed_states + .iter() + .fold(String::from("&["), |mut acc, state| { + acc.push_str(state.as_str()); + acc.push_str(", "); + acc + }); + states = states.trim().to_owned(); + states = states.trim_end_matches(',').to_owned(); + states.push(']'); + states + } + + debug!( + "Fetching videos in the states: '{}'", + states_to_string(allowed_states) + ); + let active_pick: Option = test(allowed_states, VideoStatusMarker::Pick); + let active_watch: Option = test(allowed_states, VideoStatusMarker::Watch); + let active_cached: Option = test(allowed_states, VideoStatusMarker::Cached); + let active_watched: Option = test(allowed_states, VideoStatusMarker::Watched); + let active_drop: Option = test(allowed_states, VideoStatusMarker::Drop); + let active_dropped: Option = test(allowed_states, VideoStatusMarker::Dropped); + + let videos = query!( + r" + SELECT * + FROM videos + WHERE status IN (?,?,?,?,?,?) + ORDER BY priority DESC, publish_date DESC; + ", + active_pick, + active_watch, + active_cached, + active_watched, + active_drop, + active_dropped, + ) + .fetch_all(&app.database) + .await + .with_context(|| { + format!( + "Failed to query videos with states: '{}'", + states_to_string(allowed_states) + ) + })?; + + let real_videos: Vec