about summary refs log tree commit diff stats
path: root/src/config
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 13:11:09 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 13:14:13 +0200
commit94c656ad40a7aae570e5a5fb61ad32632acc6d46 (patch)
tree269614af20caf10d76643c302e0115bd36fd2378 /src/config
parentrefactor(yt_dlp): Also move the `crates` subdirectory (diff)
downloadyt-94c656ad40a7aae570e5a5fb61ad32632acc6d46.tar.gz
yt-94c656ad40a7aae570e5a5fb61ad32632acc6d46.zip
feat(treewide): Use a configuration file
This allows use to avoid duplication of default values in the codebase
and obviously also facilitates changing these without having to
re-compile.
Diffstat (limited to '')
-rw-r--r--src/config/default.rs100
-rw-r--r--src/config/file_system.rs123
-rw-r--r--src/config/mod.rs52
3 files changed, 275 insertions, 0 deletions
diff --git a/src/config/default.rs b/src/config/default.rs
new file mode 100644
index 0000000..131c289
--- /dev/null
+++ b/src/config/default.rs
@@ -0,0 +1,100 @@
+// 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::PathBuf;
+
+use anyhow::{Context, Result};
+
+fn get_runtime_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    xdg_dirs
+        .place_runtime_file(name)
+        .with_context(|| format!("Failed to place runtime file: '{}'", name))
+}
+fn get_data_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    xdg_dirs
+        .place_data_file(name)
+        .with_context(|| format!("Failed to place data file: '{}'", name))
+}
+fn get_config_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    xdg_dirs
+        .place_config_file(name)
+        .with_context(|| format!("Failed to place config file: '{}'", name))
+}
+
+pub(super) fn create_path(path: PathBuf) -> Result<PathBuf> {
+    if !path.exists() {
+        std::fs::create_dir_all(&path)
+            .with_context(|| format!("Failed to create the '{}' directory", path.display()))?
+    }
+
+    Ok(path)
+}
+
+pub const PREFIX: &str = "yt";
+
+pub mod select {
+    pub fn playback_speed() -> f64 {
+        2.7
+    }
+    pub fn subtitle_langs() -> &'static str {
+        ""
+    }
+}
+
+pub mod watch {
+    pub fn local_comments_length() -> i64 {
+        1000
+    }
+}
+
+pub mod update {
+    pub fn max_backlog() -> i64 {
+        20
+    }
+}
+
+pub mod paths {
+    use std::{env::temp_dir, path::PathBuf};
+
+    use anyhow::Result;
+
+    use super::{create_path, get_config_path, get_data_path, get_runtime_path, PREFIX};
+
+    // We download to the temp dir to avoid taxing the disk
+    pub fn download_dir() -> Result<PathBuf> {
+        let temp_dir = temp_dir();
+
+        create_path(temp_dir.join(PREFIX))
+    }
+    pub fn mpv_config_path() -> Result<PathBuf> {
+        get_config_path("mpv.conf")
+    }
+    pub fn mpv_input_path() -> Result<PathBuf> {
+        get_config_path("mpv.input.conf")
+    }
+    pub fn database_path() -> Result<PathBuf> {
+        get_data_path("videos.sqlite")
+    }
+    pub fn config_path() -> Result<PathBuf> {
+        get_config_path("config.toml")
+    }
+    pub fn last_selection_path() -> Result<PathBuf> {
+        get_runtime_path("selected.yts")
+    }
+}
+
+pub mod download {
+    pub fn max_cache_size() -> &'static str {
+        "3 GiB"
+    }
+}
diff --git a/src/config/file_system.rs b/src/config/file_system.rs
new file mode 100644
index 0000000..8528130
--- /dev/null
+++ b/src/config/file_system.rs
@@ -0,0 +1,123 @@
+// 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 crate::config::{DownloadConfig, PathsConfig, SelectConfig, WatchConfig};
+
+use super::{
+    default::{create_path, download, paths, select, update, watch},
+    Config, UpdateConfig,
+};
+
+use std::{fs::read_to_string, path::PathBuf};
+
+use anyhow::{Context, Result};
+use toml::Table;
+use bytes::Bytes;
+
+macro_rules! get {
+    ($default:path, $config:expr, $get_fn:ident, $key_one:expr, $($keys:expr),*) => {
+        try_get!{@default $default, $config, $get_fn, $key_one, $($keys),*}
+             .with_context(|| format!("Failed to parse '{}' as a '{}'", stringify!($key_one), stringify!($get_fn)))?
+    };
+    (@path_if_none $config:expr, $option_default:expr, $default:path, $key_one:expr, $($keys:expr),*) => {
+        {
+            let maybe_download_dir =
+                try_get! {@option $config, as_str, $key_one, $($keys),*};
+
+            let down_dir = if let Some(dir) = maybe_download_dir {
+                PathBuf::from(dir)
+            } else {
+                if let Some(path) = $option_default {
+                    path
+                } else {
+                    $default()
+                        .with_context(|| format!("Failed to get default path for: '{}.{}'", stringify!($key_one), stringify!($($keys),*)))?
+                }
+            };
+            create_path(down_dir)?
+        }
+    };
+    (@path $config:expr, $default:path, $key_one:expr, $($keys:expr),*) => {
+        get! {@path_if_none $config, None, $default, $key_one, $($keys),*}
+    };
+}
+macro_rules! try_get {
+    (@option $config:expr, $get_fn:ident, $key_one:expr, $($keys:expr),*) => {
+        $config.get($key_one).map(|val| {
+            try_get! {@option val, $get_fn, $($keys),*}
+        }).flatten().flatten()
+    };
+    (@option $config:expr, $get_fn:ident, $key_one:expr) => {
+        $config.get($key_one).map(|val| val.$get_fn())
+    };
+
+    (@default $default:path, $config:expr, $get_fn:ident, $key_one:expr, $($keys:expr),*) => {
+        if let Some(a) = $config.get($key_one) {
+            try_get! {@default $default, a, $get_fn, $($keys),*}
+        } else {
+            Some($default())
+        }
+    };
+    (@default $default:path, $config:expr, $get_fn:ident, $key_one:expr) => {
+        if let Some(a) = $config.get($key_one) {
+            a.$get_fn()
+        } else {
+            Some($default())
+        }
+    };
+}
+
+impl Config {
+    pub fn from_config_file(
+        db_path: Option<PathBuf>,
+        config_path: Option<PathBuf>,
+    ) -> Result<Self> {
+        let config_file_path = config_path
+            .map(|val| Ok(val))
+            .unwrap_or_else(|| -> Result<_> { paths::config_path() })?;
+
+        let config: Table = read_to_string(config_file_path)?
+            .parse()
+            .context("Failed to parse the config file as toml")?;
+
+        Ok(Self {
+            select: SelectConfig {
+                playback_speed: get! {select::playback_speed, config, as_float, "select", "playback_speed"},
+                subtitle_langs:
+                    get! {select::subtitle_langs, config, as_str, "select", "subtitle_langs"}
+                        .to_owned(),
+            },
+            watch: WatchConfig {
+                local_comments_length: get! {watch::local_comments_length, config, as_integer, "watch", "local_comments_length"}
+                    as usize,
+            },
+            update: UpdateConfig {
+                max_backlog: get! {update::max_backlog, config, as_integer, "update", "max_backlog"}
+                    as u32,
+            },
+            paths: PathsConfig {
+                download_dir: get! {@path config, paths::download_dir, "paths", "download_dir"},
+                mpv_config_path: get! {@path config, paths::mpv_config_path, "paths", "mpv_config_path"},
+                mpv_input_path: get! {@path config, paths::mpv_input_path, "paths", "mpv_input_path"},
+                database_path: get! {@path_if_none config, db_path, paths::database_path, "paths", "database_path"},
+                last_selection_path: get! {@path config, paths::last_selection_path, "paths", "last_selection_path"},
+            },
+            download: DownloadConfig {
+                max_cache_size: {
+                    let bytes_str = get! {download::max_cache_size, config, as_str, "download", "max_cache_path"};
+                    let number: Bytes = bytes_str
+                        .parse()
+                        .context("Failed to parse max_cache_size")?;
+                    number.as_u64()
+                },
+            },
+        })
+    }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
new file mode 100644
index 0000000..8ee7cc7
--- /dev/null
+++ b/src/config/mod.rs
@@ -0,0 +1,52 @@
+// 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::PathBuf;
+
+pub mod default;
+pub mod file_system;
+
+pub struct Config {
+    pub select: SelectConfig,
+    pub watch: WatchConfig,
+    pub paths: PathsConfig,
+    pub download: DownloadConfig,
+    pub update: UpdateConfig,
+}
+pub struct UpdateConfig {
+    pub max_backlog: u32,
+}
+pub struct DownloadConfig {
+    pub max_cache_size: u64,
+}
+pub struct SelectConfig {
+    pub playback_speed: f64,
+    pub subtitle_langs: String,
+}
+pub struct WatchConfig {
+    pub local_comments_length: usize,
+}
+pub struct PathsConfig {
+    pub download_dir: PathBuf,
+    pub mpv_config_path: PathBuf,
+    pub mpv_input_path: PathBuf,
+    pub database_path: PathBuf,
+    pub last_selection_path: PathBuf,
+}
+
+// pub fn status_path() -> anyhow::Result<PathBuf> {
+//     const STATUS_PATH: &str = "running.info.json";
+//     get_runtime_path(STATUS_PATH)
+// }
+
+// pub fn subscriptions() -> anyhow::Result<PathBuf> {
+//     const SUBSCRIPTIONS: &str = "subscriptions.json";
+//     get_data_path(SUBSCRIPTIONS)
+// }