about summary refs log tree commit diff stats
path: root/src/storage/video_database/setters.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage/video_database/setters.rs')
-rw-r--r--src/storage/video_database/setters.rs270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/storage/video_database/setters.rs b/src/storage/video_database/setters.rs
new file mode 100644
index 0000000..ec5a5e1
--- /dev/null
+++ b/src/storage/video_database/setters.rs
@@ -0,0 +1,270 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// 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 <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+//! These functions change the database. They are added on a demand basis.
+
+use anyhow::Result;
+use chrono::Utc;
+use log::debug;
+use sqlx::query;
+use tokio::fs;
+
+use crate::{app::App, constants, storage::video_database::extractor_hash::ExtractorHash};
+
+use super::{Video, VideoOptions, VideoStatus};
+
+/// Set a new status for a video.
+/// This will only update the status time stamp/priority when the status or the priority has changed .
+pub async fn set_video_status(
+    app: &App,
+    video_hash: &ExtractorHash,
+    new_status: VideoStatus,
+    new_priority: Option<i64>,
+) -> Result<()> {
+    let video_hash = video_hash.hash().to_string();
+
+    let old = query!(
+        r#"
+    SELECT status, priority, cache_path
+    FROM videos
+    WHERE extractor_hash = ?
+    "#,
+        video_hash
+    )
+    .fetch_one(&app.database)
+    .await?;
+
+    let cache_path = if (VideoStatus::from_db_integer(old.status) == VideoStatus::Cached)
+        && (new_status != VideoStatus::Cached)
+    {
+        None
+    } else {
+        old.cache_path.as_deref()
+    };
+
+    let new_status = new_status.as_db_integer();
+
+    if let Some(new_priority) = new_priority {
+        if old.status == new_status && old.priority == new_priority {
+            return Ok(());
+        }
+
+        let now = Utc::now().timestamp();
+
+        debug!(
+            "Running status change: {:#?} -> {:#?}...",
+            VideoStatus::from_db_integer(old.status),
+            VideoStatus::from_db_integer(new_status),
+        );
+
+        query!(
+            r#"
+        UPDATE videos
+        SET status = ?, last_status_change = ?, priority = ?, cache_path = ?
+        WHERE extractor_hash = ?;
+        "#,
+            new_status,
+            now,
+            new_priority,
+            cache_path,
+            video_hash
+        )
+        .execute(&app.database)
+        .await?;
+    } else {
+        if old.status == new_status {
+            return Ok(());
+        }
+
+        let now = Utc::now().timestamp();
+
+        debug!(
+            "Running status change: {:#?} -> {:#?}...",
+            VideoStatus::from_db_integer(old.status),
+            VideoStatus::from_db_integer(new_status),
+        );
+
+        query!(
+            r#"
+        UPDATE videos
+        SET status = ?, last_status_change = ?, cache_path = ?
+        WHERE extractor_hash = ?;
+        "#,
+            new_status,
+            now,
+            cache_path,
+            video_hash
+        )
+        .execute(&app.database)
+        .await?;
+    }
+
+    debug!("Finished status change.");
+    Ok(())
+}
+
+/// Mark a video as watched.
+/// This will both set the status to `Watched` and the cache_path to Null.
+pub async fn set_video_watched(app: &App, video: &Video) -> Result<()> {
+    let video_hash = video.extractor_hash.hash().to_string();
+    let new_status = VideoStatus::Watched.as_db_integer();
+
+    let old = query!(
+        r#"
+    SELECT status, priority
+    FROM videos
+    WHERE extractor_hash = ?
+    "#,
+        video_hash
+    )
+    .fetch_one(&app.database)
+    .await?;
+
+    if old.status == new_status {
+        return Ok(());
+    }
+
+    let now = Utc::now().timestamp();
+
+    if let Some(path) = &video.cache_path {
+        if let Ok(true) = path.try_exists() {
+            fs::remove_file(path).await?
+        }
+    }
+
+    query!(
+        r#"
+        UPDATE videos
+        SET status = ?, last_status_change = ?, cache_path = NULL
+        WHERE extractor_hash = ?;
+        "#,
+        new_status,
+        now,
+        video_hash
+    )
+    .execute(&app.database)
+    .await?;
+
+    Ok(())
+}
+
+pub async fn set_state_change(
+    app: &App,
+    video_extractor_hash: &ExtractorHash,
+    changing: bool,
+) -> Result<()> {
+    let state_change = if changing { 1 } else { 0 };
+    let video_extractor_hash = video_extractor_hash.hash().to_string();
+
+    query!(
+        r#"
+            UPDATE videos
+            SET status_change = ?
+            WHERE extractor_hash = ?;
+        "#,
+        state_change,
+        video_extractor_hash,
+    )
+    .execute(&app.database)
+    .await?;
+
+    Ok(())
+}
+
+pub async fn set_video_options(
+    app: &App,
+    hash: ExtractorHash,
+    video_options: &VideoOptions,
+) -> Result<()> {
+    let video_extractor_hash = hash.hash().to_string();
+    let playback_speed = video_options.mpv.playback_speed;
+    let subtitle_langs = &video_options.yt_dlp.subtitle_langs;
+
+    query!(
+        r#"
+            UPDATE video_options
+            SET playback_speed = ?, subtitle_langs = ?
+            WHERE extractor_hash = ?;
+        "#,
+        playback_speed,
+        subtitle_langs,
+        video_extractor_hash,
+    )
+    .execute(&app.database)
+    .await?;
+
+    Ok(())
+}
+
+pub async fn add_video(app: &App, video: Video) -> Result<()> {
+    let parent_subscription_name = if let Some(subs) = video.parent_subscription_name {
+        subs
+    } else {
+        "NULL".to_owned()
+    };
+
+    let thumbnail_url = if let Some(thum) = video.thumbnail_url {
+        thum.to_string()
+    } else {
+        "NULL".to_owned()
+    };
+
+    let status = video.status.as_db_integer();
+    let status_change = if video.status_change { 1 } else { 0 };
+    let url = video.url.to_string();
+    let extractor_hash = video.extractor_hash.hash().to_string();
+
+    let default_subtitle_langs = constants::DEFAULT_SUBTITLE_LANGS;
+    let default_mpv_playback_speed = constants::DEFAULT_MPV_PLAYBACK_SPEED;
+
+    query!(
+        r#"
+        BEGIN;
+        INSERT INTO videos (
+            parent_subscription_name,
+            status,
+            status_change,
+            last_status_change,
+            title,
+            url,
+            description,
+            duration,
+            publish_date,
+            thumbnail_url,
+            extractor_hash)
+        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
+
+        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)
+    .await?;
+
+    Ok(())
+}