aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/yt/src/commands/update/implm/mod.rs81
-rw-r--r--crates/yt/src/commands/update/mod.rs4
-rw-r--r--crates/yt/src/storage/db/get/video/mod.rs36
-rw-r--r--crates/yt/src/storage/db/video/mod.rs13
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)