diff options
Diffstat (limited to '')
| -rw-r--r-- | crates/yt/src/commands/update/implm/mod.rs | 81 | ||||
| -rw-r--r-- | crates/yt/src/commands/update/mod.rs | 4 | ||||
| -rw-r--r-- | crates/yt/src/storage/db/get/video/mod.rs | 36 | ||||
| -rw-r--r-- | crates/yt/src/storage/db/video/mod.rs | 13 |
4 files changed, 125 insertions, 9 deletions
diff --git a/crates/yt/src/commands/update/implm/mod.rs b/crates/yt/src/commands/update/implm/mod.rs index 10626ac..b3064c1 100644 --- a/crates/yt/src/commands/update/implm/mod.rs +++ b/crates/yt/src/commands/update/implm/mod.rs @@ -11,13 +11,18 @@ use crate::{ app::App, commands::update::{UpdateCommand, implm::updater::Updater}, + select::duration::MaybeDuration, storage::db::{ extractor_hash::ExtractorHash, subscription::{Subscription, Subscriptions}, + video::{TimeStamp, Video}, }, }; use anyhow::{Result, bail}; +use chrono::TimeDelta; +use colors::{Colorize, IntoCanvas}; +use log::info; mod updater; @@ -26,6 +31,7 @@ impl UpdateCommand { let UpdateCommand { max_backlog, subscriptions: subscription_names_to_update, + force, } = self; let mut all_subs = Subscriptions::get(app).await?.remove_inactive(); @@ -42,20 +48,89 @@ impl UpdateCommand { Ok(val) } else { bail!( - "Your specified subscription to update '{}' is not a subscription!", - sub + "Your specified subscription to update '{sub}' is not a subscription!", ) } }) .collect::<Result<_>>()? }; + let real_subs = if force { + subs + } else { + // We only update these subscriptions if their last videos has been uploaded for longer than + // the average time between uploads. + + let mut real_subs = vec![]; + for sub in subs { + let mut videos: Vec<TimeStamp> = Video::from_subscription(&sub, app) + .await? + .into_iter() + .filter_map(|v| v.publish_date) + .collect(); + + if videos.is_empty() { + real_subs.push(sub); + break; + } + + videos.sort(); + + let average_time_between_videos: TimeDelta = { + let mut videos = videos.iter().copied(); + let mut last = videos.next().expect("we removed the empty video case"); + + let mut deltas = vec![]; + for next in videos { + deltas.push(next - last); + last = next; + } + + let sum: TimeDelta = deltas.iter().sum(); + + sum / deltas.len() as i32 + }; + + let last_video = videos.last().expect("to exist"); + + if average_time_between_videos <= (TimeStamp::from_now() - *last_video) { + real_subs.push(sub); + } else { + info!( + "Not adding '{}', as the average time between videos ({:#}) is longer than the last upload ({} {} ago) (use --force to override)", + sub.name.blue().render(app.config.global.display_colors), + MaybeDuration::from_std( + average_time_between_videos + .to_std() + .expect("Should be strictly positive") + ) + .green() + .bold() + .render(app.config.global.display_colors), + last_video + .magenta() + .render(app.config.global.display_colors), + MaybeDuration::from_std( + (TimeStamp::from_now() - *last_video) + .to_std() + .expect("Should be strictly positive") + ) + .green() + .bold() + .render(app.config.global.display_colors), + ); + } + } + + real_subs + }; + // We can get away with not having to re-fetch the hashes every time, as the returned video // should not contain duplicates. let hashes = ExtractorHash::get_all(app).await?; let updater = Updater::new(max_backlog, app.config.update.pool_size, hashes); - updater.update(app, subs).await?; + updater.update(app, real_subs).await?; Ok(()) } diff --git a/crates/yt/src/commands/update/mod.rs b/crates/yt/src/commands/update/mod.rs index cb29148..f181626 100644 --- a/crates/yt/src/commands/update/mod.rs +++ b/crates/yt/src/commands/update/mod.rs @@ -21,6 +21,10 @@ pub(super) struct UpdateCommand { #[arg(short, long)] max_backlog: Option<usize>, + /// Update all specified subscriptions, regardless of average time between uploads + #[arg(short, long)] + force: bool, + /// The subscriptions to update #[arg(add = ArgValueCompleter::new(complete_subscription))] subscriptions: Vec<String>, diff --git a/crates/yt/src/storage/db/get/video/mod.rs b/crates/yt/src/storage/db/get/video/mod.rs index 69adb6b..c2edbdd 100644 --- a/crates/yt/src/storage/db/get/video/mod.rs +++ b/crates/yt/src/storage/db/get/video/mod.rs @@ -17,10 +17,13 @@ use yt_dlp::{info_json::InfoJson, json_cast, json_try_get}; use crate::{ app::App, - storage::db::video::{ - Video, VideoStatus, VideoStatusMarker, - comments::{Comments, raw::RawComment}, - video_from_record, + storage::db::{ + subscription::Subscription, + video::{ + Video, VideoStatus, VideoStatusMarker, + comments::{Comments, raw::RawComment}, + video_from_record, + }, }, }; @@ -187,6 +190,31 @@ impl Video { } } + pub(crate) async fn from_subscription(sub: &Subscription, app: &App) -> Result<Vec<Self>> { + debug!("Fetching videos from subscription: '{}'", sub.name); + + // NOTE: The ORDER BY statement should be the same as the one in [`next_to_download`]. <2024-08-22> + let videos = query!( + r" + SELECT * + FROM videos + WHERE parent_subscription_name = ? + ORDER BY priority DESC, publish_date DESC; + ", + sub.name + ) + .fetch_all(&app.database) + .await + .with_context(|| format!("Failed to query videos from subscription: '{}'", sub.name))?; + + let real_videos: Vec<Video> = videos + .iter() + .map(|base| -> Video { video_from_record!(base) }) + .collect(); + + Ok(real_videos) + } + /// Returns the videos that are in the `allowed_states`. /// /// # Panics diff --git a/crates/yt/src/storage/db/video/mod.rs b/crates/yt/src/storage/db/video/mod.rs index d8d712f..7fc6764 100644 --- a/crates/yt/src/storage/db/video/mod.rs +++ b/crates/yt/src/storage/db/video/mod.rs @@ -10,7 +10,7 @@ use std::{fmt::Display, path::PathBuf, time::Duration}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, TimeDelta, Utc}; use serde::{Deserialize, Serialize}; use url::Url; @@ -125,7 +125,7 @@ impl Display for Priority { } /// An UNIX time stamp. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct TimeStamp { value: i64, } @@ -150,6 +150,15 @@ impl TimeStamp { } } } + +impl std::ops::Sub for TimeStamp { + type Output = TimeDelta; + + fn sub(self, rhs: Self) -> Self::Output { + TimeDelta::seconds(self.value - rhs.value) + } +} + impl Display for TimeStamp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { DateTime::from_timestamp(self.value, 0) |
