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/download/mod.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/download/mod.rs')
-rw-r--r-- | src/download/mod.rs | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/src/download/mod.rs b/src/download/mod.rs new file mode 100644 index 0000000..62fae84 --- /dev/null +++ b/src/download/mod.rs @@ -0,0 +1,140 @@ +// 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::time::Duration; + +use crate::{ + app::App, + download::download_options::download_opts, + storage::video_database::{ + downloader::{get_next_uncached_video, set_video_cache_path}, extractor_hash::ExtractorHash, getters::get_video_yt_dlp_opts, Video + }, +}; + +use anyhow::{Context, Result}; +use log::{debug, info}; +use tokio::{task::JoinHandle, time}; + +mod download_options; + +#[derive(Debug)] +pub struct CurrentDownload { + task_handle: JoinHandle<Result<()>>, + extractor_hash: ExtractorHash, +} + +impl CurrentDownload { + fn new_from_video(video: Video) -> Self { + let extractor_hash = video.extractor_hash.clone(); + + let task_handle = tokio::spawn(async move { + // FIXME: Remove this app reconstruction <2024-07-29> + let new_app = App::new().await?; + + Downloader::actually_cache_video(&new_app, &video) + .await + .with_context(|| format!("Failed to cache video: '{}'", video.title))?; + Ok(()) + }); + + Self { + task_handle, + extractor_hash, + } + } +} + +pub struct Downloader { + current_download: Option<CurrentDownload>, +} + +impl Downloader { + pub fn new() -> Self { + Self { + current_download: None, + } + } + + /// The entry point to the Downloader. + /// This Downloader will periodically check if the database has changed, and then also + /// change which videos it downloads. + /// This will run, until the database doesn't contain any watchable videos + pub async fn consume(&mut self, app: &App) -> Result<()> { + while let Some(next_video) = get_next_uncached_video(app).await? { + if let Some(_) = &self.current_download { + let current_download = self.current_download.take().expect("Is Some"); + + if current_download.task_handle.is_finished() { + current_download.task_handle.await??; + continue; + } + + if next_video.extractor_hash != current_download.extractor_hash { + info!( + "Noticed, that the next video is not the video being downloaded, replacing it ('{}' vs. '{}')!", + next_video.extractor_hash.into_short_hash(app).await?, current_download.extractor_hash.into_short_hash(app).await? + ); + + // Replace the currently downloading video + current_download.task_handle.abort(); + + let new_current_download = CurrentDownload::new_from_video(next_video); + + self.current_download = Some(new_current_download); + } else { + debug!( + "Currently downloading '{}'", + current_download.extractor_hash.into_short_hash(app).await? + ); + // Reset the taken value + self.current_download = Some(current_download); + time::sleep(Duration::new(1, 0)).await; + } + } else { + info!( + "No video is being downloaded right now, setting it to '{}'", + next_video.title + ); + let new_current_download = CurrentDownload::new_from_video(next_video); + self.current_download = Some(new_current_download); + } + + // if get_allocated_cache().await? < CONCURRENT { + // .cache_video(next_video).await?; + // } + } + + info!("Finished downloading!"); + Ok(()) + } + + async fn actually_cache_video(app: &App, video: &Video) -> Result<()> { + debug!("Download started: {}", &video.title); + + let addional_opts = get_video_yt_dlp_opts(&app, &video.extractor_hash).await?; + + let result = yt_dlp::download(&[video.url.clone()], &download_opts(addional_opts)) + .await + .with_context(|| format!("Failed to download video: '{}'", video.title))?; + + assert_eq!(result.len(), 1); + let result = &result[0]; + + set_video_cache_path(app, &video.extractor_hash, Some(&result)).await?; + + info!( + "Video '{}' was downlaoded to path: {}", + video.title, + result.display() + ); + + Ok(()) + } +} |