about summary refs log tree commit diff stats
path: root/src/storage/video_database/downloader.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-21 10:49:23 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-21 11:28:43 +0200
commit1debeb77f7986de1b659dcfdc442de6415e1d9f5 (patch)
tree4df3e7c3f6a2d1ec116e4088c5ace7f143a8b05f /src/storage/video_database/downloader.rs
downloadyt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.tar.gz
yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.zip
chore: Initial Commit
This repository was migrated out of my nixos-config.
Diffstat (limited to 'src/storage/video_database/downloader.rs')
-rw-r--r--src/storage/video_database/downloader.rs210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/storage/video_database/downloader.rs b/src/storage/video_database/downloader.rs
new file mode 100644
index 0000000..c04ab8d
--- /dev/null
+++ b/src/storage/video_database/downloader.rs
@@ -0,0 +1,210 @@
+// 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>.
+
+use std::path::{Path, PathBuf};
+
+use anyhow::Result;
+use log::debug;
+use sqlx::query;
+use url::Url;
+
+use crate::{app::App, storage::video_database::VideoStatus};
+
+use super::{ExtractorHash, Video};
+
+/// Returns to next video which should be downloaded. This respects the priority assigned by select.
+/// It does not return videos, which are already cached.
+pub async fn get_next_uncached_video(app: &App) -> Result<Option<Video>> {
+    let status = VideoStatus::Watch.as_db_integer();
+
+    let result = query!(
+        r#"
+        SELECT *
+        FROM videos
+        WHERE status = ? AND cache_path IS NULL
+        ORDER BY priority ASC
+        LIMIT 1;
+    "#,
+        status
+    )
+    .fetch_one(&app.database)
+    .await;
+
+    if let Err(sqlx::Error::RowNotFound) = result {
+        Ok(None)
+    } else {
+        let base = result?;
+
+        let thumbnail_url = if let Some(url) = &base.thumbnail_url {
+            Some(Url::parse(&url)?)
+        } else {
+            None
+        };
+
+        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(|val| PathBuf::from(val)),
+            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)?,
+        };
+
+        Ok(Some(video))
+    }
+}
+
+/// Returns to next video which can be watched (i.e. is cached).
+/// This respects the priority assigned by select.
+pub async fn get_next_video_watchable(app: &App) -> Result<Option<Video>> {
+    let result = query!(
+        r#"
+        SELECT *
+        FROM videos
+        WHERE status = 'Watching' AND cache_path IS NOT NULL
+        ORDER BY priority ASC
+        LIMIT 1;
+    "#
+    )
+    .fetch_one(&app.database)
+    .await;
+
+    if let Err(sqlx::Error::RowNotFound) = result {
+        Ok(None)
+    } else {
+        let base = result?;
+
+        let thumbnail_url = if let Some(url) = &base.thumbnail_url {
+            Some(Url::parse(&url)?)
+        } else {
+            None
+        };
+
+        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(|val| PathBuf::from(val)),
+            description: base.description.clone(),
+            duration: base.duration,
+            extractor_hash: ExtractorHash::from_hash(
+                base.extractor_hash
+                    .parse()
+                    .expect("The db extractor_hash should be valid blake3 hash"),
+            ),
+            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)?,
+        };
+
+        Ok(Some(video))
+    }
+}
+
+/// Update the cached path of a video. Will be set to NULL if the path is None
+/// This will also set the status to `Cached` when path is Some, otherwise it set's the status to
+/// `Watch`.
+pub async fn set_video_cache_path(
+    app: &App,
+    video: &ExtractorHash,
+    path: Option<&Path>,
+) -> Result<()> {
+    if let Some(path) = path {
+        debug!(
+            "Setting cache path from '{}' to '{}'",
+            video.into_short_hash(app).await?,
+            path.display()
+        );
+
+        let path_str = path.display().to_string();
+        let extractor_hash = video.hash().to_string();
+        let status = VideoStatus::Cached.as_db_integer();
+
+        query!(
+            r#"
+            UPDATE videos
+            SET cache_path = ?, status = ?
+            WHERE extractor_hash = ?;
+        "#,
+            path_str,
+            status,
+            extractor_hash
+        )
+        .execute(&app.database)
+        .await?;
+
+        Ok(())
+    } else {
+        debug!(
+            "Setting cache path from '{}' to NULL",
+            video.into_short_hash(app).await?,
+        );
+
+        let extractor_hash = video.hash().to_string();
+        let status = VideoStatus::Watch.as_db_integer();
+
+        query!(
+            r#"
+            UPDATE videos
+            SET cache_path = NULL, status = ?
+            WHERE extractor_hash = ?;
+        "#,
+            status,
+            extractor_hash
+        )
+        .execute(&app.database)
+        .await?;
+
+        Ok(())
+    }
+}
+
+/// Returns the number of cached videos
+pub async fn get_allocated_cache(app: &App) -> Result<u32> {
+    let count = query!(
+        r#"
+        SELECT COUNT(cache_path) as count
+        FROM videos
+        WHERE cache_path IS NOT NULL;
+"#,
+    )
+    .fetch_one(&app.database)
+    .await?;
+
+    Ok(count.count as u32)
+}