diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 10:49:23 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 11:28:43 +0200 |
commit | 1debeb77f7986de1b659dcfdc442de6415e1d9f5 (patch) | |
tree | 4df3e7c3f6a2d1ec116e4088c5ace7f143a8b05f /src/storage/video_database/downloader.rs | |
download | yt-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.rs | 210 |
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) +} |