aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--yt/src/cache/mod.rs10
-rw-r--r--yt/src/status/mod.rs29
-rw-r--r--yt/src/storage/video_database/downloader.rs38
-rw-r--r--yt/src/storage/video_database/getters.rs99
-rw-r--r--yt/src/storage/video_database/setters.rs108
-rw-r--r--yt/src/update/mod.rs5
6 files changed, 134 insertions, 155 deletions
diff --git a/yt/src/cache/mod.rs b/yt/src/cache/mod.rs
index e3dfda2..6cd240c 100644
--- a/yt/src/cache/mod.rs
+++ b/yt/src/cache/mod.rs
@@ -16,7 +16,6 @@ use crate::{
app::App,
storage::video_database::{
Video, VideoStatus, downloader::set_video_cache_path, getters::get_videos,
- setters::set_state_change,
},
};
@@ -90,13 +89,8 @@ pub async fn maintain(app: &App, all: bool) -> Result<()> {
invalidate_video(app, &vid, false).await?;
}
}
- if vid.status_change {
- info!(
- "Video '{}' has it's changing bit set. This is probably the result of an unexpectet exit. Clearing it",
- vid.title
- );
- set_state_change(app, &vid.extractor_hash, false).await?;
- }
+
+ // TODO(@bpeetz): Check if only one video is set `is_focused`. <2025-02-17>
}
Ok(())
diff --git a/yt/src/status/mod.rs b/yt/src/status/mod.rs
index 56d29e6..345ae3c 100644
--- a/yt/src/status/mod.rs
+++ b/yt/src/status/mod.rs
@@ -38,13 +38,6 @@ macro_rules! get {
.filter(|vid| vid.status == VideoStatus::$status)
.collect()
};
-
- (@changing $videos:expr, $status:ident) => {
- $videos
- .iter()
- .filter(|vid| vid.status == VideoStatus::$status && vid.status_change)
- .count()
- };
}
pub async fn show(app: &App) -> Result<()> {
@@ -75,16 +68,6 @@ pub async fn show(app: &App) -> Result<()> {
let drop_videos_len = get!(all_videos, Drop);
let dropped_videos_len = get!(all_videos, Dropped);
- // changing
- let picked_videos_changing = get!(@changing all_videos, Pick);
-
- let watch_videos_changing = get!(@changing all_videos, Watch);
- let cached_videos_changing = get!(@changing all_videos, Cached);
- let watched_videos_changing = get!(@changing all_videos, Watched);
-
- let drop_videos_changing = get!(@changing all_videos, Drop);
- let dropped_videos_changing = get!(@changing all_videos, Dropped);
-
let subscriptions = get(app).await?;
let subscriptions_len = subscriptions.0.len();
@@ -127,14 +110,14 @@ pub async fn show(app: &App) -> Result<()> {
let cache_usage: Bytes = cache_usage_raw;
println!(
"\
-Picked Videos: {picked_videos_len} ({picked_videos_changing} changing)
+Picked Videos: {picked_videos_len}
-Watch Videos: {watch_videos_len} ({watch_videos_changing} changing)
-Cached Videos: {cached_videos_len} ({cached_videos_changing} changing)
-Watched Videos: {watched_videos_len} ({watched_videos_changing} changing)
+Watch Videos: {watch_videos_len}
+Cached Videos: {cached_videos_len}
+Watched Videos: {watched_videos_len}
-Drop Videos: {drop_videos_len} ({drop_videos_changing} changing)
-Dropped Videos: {dropped_videos_len} ({dropped_videos_changing} changing)
+Drop Videos: {drop_videos_len}
+Dropped Videos: {dropped_videos_len}
{watchtime_status}
diff --git a/yt/src/storage/video_database/downloader.rs b/yt/src/storage/video_database/downloader.rs
index bfb7aa3..d8b2041 100644
--- a/yt/src/storage/video_database/downloader.rs
+++ b/yt/src/storage/video_database/downloader.rs
@@ -15,7 +15,9 @@ use log::debug;
use sqlx::query;
use url::Url;
-use crate::{app::App, storage::video_database::VideoStatus, unreachable::Unreachable};
+use crate::{
+ app::App, storage::video_database::VideoStatus, unreachable::Unreachable, video_from_record,
+};
use super::{ExtractorHash, Video};
@@ -46,39 +48,7 @@ pub async fn get_next_uncached_video(app: &App) -> Result<Option<Video>> {
} else {
let base = result?;
- let thumbnail_url = base
- .thumbnail_url
- .as_ref()
- .map(|url| Url::parse(url).unreachable("Parsing this as url should always work"));
-
- let status_change = if base.status_change == 1 {
- true
- } else {
- assert_eq!(base.status_change, 0, "Can only be 1 or 0");
- false
- };
-
- let video = Video {
- cache_path: base.cache_path.as_ref().map(PathBuf::from),
- description: base.description.clone(),
- duration: base.duration,
- extractor_hash: ExtractorHash::from_hash(
- base.extractor_hash
- .parse()
- .expect("The hash in the db should be valid"),
- ),
- last_status_change: base.last_status_change,
- parent_subscription_name: base.parent_subscription_name.clone(),
- priority: base.priority,
- publish_date: base.publish_date,
- status: VideoStatus::from_db_integer(base.status),
- status_change,
- thumbnail_url,
- title: base.title.clone(),
- url: Url::parse(&base.url).expect("Parsing this as url should always work"),
- };
-
- Ok(Some(video))
+ Ok(Some(video_from_record! {base}))
}
}
diff --git a/yt/src/storage/video_database/getters.rs b/yt/src/storage/video_database/getters.rs
index 3470442..09cc9ee 100644
--- a/yt/src/storage/video_database/getters.rs
+++ b/yt/src/storage/video_database/getters.rs
@@ -8,7 +8,7 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-//! These functions interact with the storage db in a read-only way. They are added on-demaned (as
+//! 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};
@@ -24,22 +24,17 @@ use crate::{
app::App,
storage::{
subscriptions::Subscription,
- video_database::{Video, extractor_hash::ExtractorHash},
+ video_database::{InPlaylist, Video, extractor_hash::ExtractorHash},
},
unreachable::Unreachable,
};
use super::{MpvOptions, VideoOptions, VideoStatus, YtDlpOptions};
+#[macro_export]
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 {
+ Video {
cache_path: $record.cache_path.as_ref().map(|val| PathBuf::from(val)),
description: $record.description.clone(),
duration: $record.duration,
@@ -53,17 +48,29 @@ macro_rules! video_from_record {
parent_subscription_name: $record.parent_subscription_name.clone(),
publish_date: $record.publish_date,
status: VideoStatus::from_db_integer($record.status),
- thumbnail_url,
+ thumbnail_url: if let Some(url) = &$record.thumbnail_url {
+ Some(Url::parse(&url).expect("Parsing this as url should always work"))
+ } else {
+ None
+ },
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
+
+ in_playlist: {
+ if $record.in_playlist == 1 && $record.is_focused == 1 {
+ super::InPlaylist::Focused
+ } else if $record.in_playlist == 1 && $record.is_focused == 0 {
+ super::InPlaylist::Hidden
+ } else if $record.in_playlist == 0 && $record.is_focused == 0 {
+ super::InPlaylist::Excluded
+ } else {
+ unreachable!("Other combinations should not exist")
+ }
},
- })
+
+ watch_progress: $record.watch_progress as u32,
+ }
};
}
@@ -159,15 +166,22 @@ pub async fn get_videos(
As it was an URL when we put it in.",
),
priority: base.get("priority"),
- status_change: {
- let val = base.get::<i64, &str>("status_change");
- if val == 1 {
- true
+
+ in_playlist: {
+ let in_playlist = base.get::<u8, &str>("in_playlist");
+ let is_focused = base.get::<u8, &str>("is_focused");
+
+ if in_playlist == 1 && is_focused == 1 {
+ InPlaylist::Focused
+ } else if in_playlist == 1 && is_focused == 0 {
+ InPlaylist::Hidden
+ } else if in_playlist == 0 && is_focused == 0 {
+ InPlaylist::Excluded
} else {
- assert_eq!(val, 0, "Can only be 1 or 0");
- false
+ unreachable!("Other combinations should not be possible")
}
},
+ watch_progress: base.get::<i64, &str>("watch_progress") as u32,
})
})
.collect::<Result<Vec<Video>>>()?;
@@ -204,51 +218,24 @@ pub async fn get_video_by_hash(app: &App, hash: &ExtractorHash) -> Result<Video>
.fetch_one(&app.database)
.await?;
- video_from_record! {raw_video}
+ Ok(video_from_record! {raw_video})
}
/// # Panics
/// Only if assertions fail.
pub async fn get_currently_playing_video(app: &App) -> Result<Option<Video>> {
- let mut videos: Vec<Video> = get_changing_videos(app, VideoStatus::Cached).await?;
+ let record = query!("SELECT * FROM videos WHERE is_focused = 1 AND in_playlist = 1")
+ .fetch_one(&app.database)
+ .await;
- if videos.is_empty() {
+ if let Err(sqlx::Error::RowNotFound) = record {
Ok(None)
} else {
- assert_eq!(
- videos.len(),
- 1,
- "Only one video can change from cached to watched at once!"
- );
-
- Ok(Some(videos.remove(0)))
+ let base = record?;
+ Ok(Some(video_from_record! {base}))
}
}
-pub async fn get_changing_videos(app: &App, old_state: VideoStatus) -> Result<Vec<Video>> {
- let status = old_state.as_db_integer();
-
- let matching = query!(
- r#"
- SELECT *
- FROM videos
- WHERE status_change = 1 AND status = ?;
- "#,
- status
- )
- .fetch_all(&app.database)
- .await?;
-
- let real_videos: Vec<Video> = matching
- .iter()
- .map(|base| -> Result<Video> {
- video_from_record! {base}
- })
- .collect::<Result<Vec<Video>>>()?;
-
- Ok(real_videos)
-}
-
pub async fn get_all_hashes(app: &App) -> Result<Vec<Hash>> {
let hashes_hex = query!(
r#"
diff --git a/yt/src/storage/video_database/setters.rs b/yt/src/storage/video_database/setters.rs
index 4531fd1..32a745b 100644
--- a/yt/src/storage/video_database/setters.rs
+++ b/yt/src/storage/video_database/setters.rs
@@ -16,7 +16,12 @@ use log::{debug, info};
use sqlx::query;
use tokio::fs;
-use crate::{app::App, storage::video_database::extractor_hash::ExtractorHash};
+use crate::{
+ app::App,
+ storage::video_database::{
+ extractor_hash::ExtractorHash, getters::get_currently_playing_video,
+ },
+};
use super::{Video, VideoOptions, VideoStatus};
@@ -166,26 +171,46 @@ pub async fn set_video_watched(app: &App, video: &Video) -> Result<()> {
Ok(())
}
-pub async fn set_state_change(
+/// Set a video to be focused.
+/// This optionally takes the `old_video_hash` to disable.
+pub async fn set_focused(
app: &App,
- video_extractor_hash: &ExtractorHash,
- changing: bool,
+ new_video_hash: &ExtractorHash,
+ old_video_hash: Option<&ExtractorHash>,
) -> Result<()> {
- let state_change = u32::from(changing);
- let video_extractor_hash = video_extractor_hash.hash().to_string();
+ if let Some(old) = old_video_hash {
+ let hash = old.hash().to_string();
+ query!(
+ r#"
+ UPDATE videos
+ SET is_focused = 0
+ WHERE extractor_hash = ?;
+ "#,
+ hash
+ )
+ .execute(&app.database)
+ .await?;
+ }
+ let new_hash = new_video_hash.hash().to_string();
query!(
r#"
UPDATE videos
- SET status_change = ?
+ SET is_focused = 1
WHERE extractor_hash = ?;
"#,
- state_change,
- video_extractor_hash,
+ new_hash,
)
.execute(&app.database)
.await?;
+ assert_eq!(
+ *new_video_hash,
+ get_currently_playing_video(app)
+ .await?
+ .expect("This is some at this point")
+ .extractor_hash
+ );
Ok(())
}
@@ -220,54 +245,73 @@ pub async fn add_video(app: &App, video: Video) -> Result<()> {
let thumbnail_url = video.thumbnail_url.map(|val| val.to_string());
let status = video.status.as_db_integer();
- let status_change = u32::from(video.status_change);
let url = video.url.to_string();
let extractor_hash = video.extractor_hash.hash().to_string();
let default_subtitle_langs = &app.config.select.subtitle_langs;
let default_mpv_playback_speed = app.config.select.playback_speed;
+ let (in_playlist, is_focused) = {
+ match video.in_playlist {
+ super::InPlaylist::Excluded => (false, false),
+ super::InPlaylist::Hidden => (true, false),
+ super::InPlaylist::Focused => (true, true),
+ }
+ };
+
+ let mut tx = app.database.begin().await?;
query!(
r#"
- BEGIN;
INSERT INTO videos (
- parent_subscription_name,
- status,
- status_change,
- last_status_change,
- title,
- url,
description,
duration,
+ extractor_hash,
+ in_playlist,
+ is_focused,
+ last_status_change,
+ parent_subscription_name,
publish_date,
+ status,
thumbnail_url,
- extractor_hash)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
+ title,
+ url,
+ watch_progress
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
+ "#,
+ video.description,
+ video.duration,
+ extractor_hash,
+ in_playlist,
+ is_focused,
+ video.last_status_change,
+ parent_subscription_name,
+ video.publish_date,
+ status,
+ thumbnail_url,
+ video.title,
+ url,
+ video.watch_progress
+ )
+ .execute(&mut *tx)
+ .await?;
+ query!(
+ r#"
INSERT INTO video_options (
extractor_hash,
subtitle_langs,
playback_speed)
VALUES (?, ?, ?);
- COMMIT;
"#,
- parent_subscription_name,
- status,
- status_change,
- video.last_status_change,
- video.title,
- url,
- video.description,
- video.duration,
- video.publish_date,
- thumbnail_url,
- extractor_hash,
extractor_hash,
default_subtitle_langs,
default_mpv_playback_speed
)
- .execute(&app.database)
+ .execute(&mut *tx)
.await?;
+ tx.commit().await?;
+
Ok(())
}
diff --git a/yt/src/update/mod.rs b/yt/src/update/mod.rs
index 9d34498..7bd37b6 100644
--- a/yt/src/update/mod.rs
+++ b/yt/src/update/mod.rs
@@ -21,7 +21,7 @@ use crate::{
storage::{
subscriptions::{self, Subscription},
video_database::{
- Video, VideoStatus, extractor_hash::ExtractorHash, getters::get_all_hashes,
+ InPlaylist, Video, VideoStatus, extractor_hash::ExtractorHash, getters::get_all_hashes,
setters::add_video,
},
},
@@ -170,10 +170,11 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
priority: 0,
publish_date,
status: VideoStatus::Pick,
- status_change: false,
thumbnail_url,
title: unwrap_option!(entry.title.clone()),
url,
+ in_playlist: InPlaylist::Excluded,
+ watch_progress: 0,
};
Ok(video)
}