about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-17 19:28:55 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-17 19:29:38 +0100
commit74346a5e43235be37bffca6dd0cb0ead66a529b5 (patch)
tree81acd823ef58b906734efb94d11998c51384ecfa
parentfeat(yt/storage/migrate): Add db version One (diff)
downloadyt-74346a5e43235be37bffca6dd0cb0ead66a529b5.zip
fix(yt): Remove most of the references to the zero version `Video` struct
-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)
 }