about summary refs log tree commit diff stats
path: root/src/storage/video_database/downloader.rs
blob: c5490f313fb8cd3404867e8d1bd414bcb03ef1e8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 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();

    // NOTE: The ORDER BY statement should be the same as the one in [`getters::get_videos`].<2024-08-22>
    let result = query!(
        r#"
        SELECT *
        FROM videos
        WHERE status = ? AND cache_path IS NULL
        ORDER BY priority DESC, publish_date DESC
        LIMIT 1;
    "#,
        status
    )
    .fetch_one(&app.database)
    .await;

    if let Err(sqlx::Error::RowNotFound) = result {
        Ok(None)
    } else {
        let base = result?;

        let thumbnail_url = base.thumbnail_url.as_ref().map(|url| Url::parse(url).expect("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))
    }
}

/// 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)
}