aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/mp/mpdpopm/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/config.rs189
1 files changed, 189 insertions, 0 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/config.rs b/pkgs/by-name/mp/mpdpopm/src/config.rs
new file mode 100644
index 00000000..8bb5abfb
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/config.rs
@@ -0,0 +1,189 @@
+// Copyright (C) 2021-2025 Michael herstine <sp1ff@pobox.com>
+//
+// This file is part of mpdpopm.
+//
+// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+// Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with mpdpopm. If not,
+// see <http://www.gnu.org/licenses/>.
+
+//! # mpdpopm Configuration
+//!
+//! ## Introduction
+//!
+//! This module defines the configuration struct & handles deserialization thereof.
+//!
+//! ## Discussion
+//!
+//! In the first releases of [mpdpopm](crate) I foolishly forgot to add a version field to the
+//! configuration structure. I am now paying for my sin by having to attempt serializing two
+//! versions until one succeeds.
+//!
+//! The idiomatic approach to versioning [serde](https://docs.serde.rs/serde/) structs seems to be
+//! using an
+//! [enumeration](https://www.reddit.com/r/rust/comments/44dds3/handling_multiple_file_versions_with_serde_or/). This
+//! implementation *now* uses that, but that leaves us with the problem of handling the initial,
+//! un-tagged version. I proceed as follows:
+//!
+//! 1. attempt to deserialize as a member of the modern enumeration
+//! 2. if that succeeds, with the most-recent version, we're good
+//! 3. if that succeeds with an archaic version, convert to the most recent and warn the user
+//! 4. if that fails, attempt to deserialize as the initial struct version
+//! 5. if that succeeds, convert to the most recent & warn the user
+//! 6. if that fails, I'm kind of stuck because I don't know what the user was trying to express;
+//! bundle-up all the errors, report 'em & urge the user to use the most recent version
+use crate::vars::{LOCALSTATEDIR, PREFIX};
+
+use anyhow::{Result, bail};
+use serde::{Deserialize, Serialize};
+
+use std::{env, path::PathBuf};
+
+/// [mpdpopm](crate) can communicate with MPD over either a local Unix socket, or over regular TCP
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub enum Connection {
+ /// Local Unix socket-- payload is the path to the socket
+ Local { path: PathBuf },
+ /// TCP-- payload is the hostname & port number
+ TCP { host: String, port: u16 },
+}
+
+impl Connection {
+ pub fn new() -> Result<Self> {
+ let env = match env::var("MPD_HOST") {
+ Ok(env) => Some(env),
+ Err(err) => match err {
+ env::VarError::NotPresent => None,
+ env::VarError::NotUnicode(_) => {
+ bail!("Failed to get `MPD_HOST` env var: {err}")
+ }
+ },
+ }
+ .unwrap_or("/run/mpd/socket".to_owned());
+
+ if env.starts_with("/") {
+ // We assume that this is a path to a local socket
+ Ok(Self::Local {
+ path: PathBuf::from(env),
+ })
+ } else {
+ todo!("Not yet able to auto-parse, MPD_HOST for remote connection")
+ }
+ }
+}
+
+impl Default for Connection {
+ fn default() -> Self {
+ Self::new().expect("Could not generate default connection")
+ }
+}
+
+#[cfg(test)]
+mod test_connection {
+ use super::Connection;
+
+ #[test]
+ fn test_serde() {
+ use serde_json::to_string;
+
+ use std::path::PathBuf;
+
+ let text = to_string(&Connection::Local {
+ path: PathBuf::from("/var/run/mpd.sock"),
+ })
+ .unwrap();
+
+ assert_eq!(
+ text,
+ String::from(r#"{"Local":{"path":"/var/run/mpd.sock"}}"#)
+ );
+
+ let text = to_string(&Connection::TCP {
+ host: String::from("localhost"),
+ port: 6600,
+ })
+ .unwrap();
+ assert_eq!(
+ text,
+ String::from(r#"{"TCP":{"host":"localhost","port":6600}}"#)
+ );
+ }
+}
+
+/// THe possible start-up mode.
+#[derive(Default, Deserialize, Debug, Serialize)]
+pub enum Mode {
+ #[default]
+ /// Don't do anything special
+ Normal,
+
+ /// Already start the DJ mode on start-up
+ Dj,
+}
+
+/// This is the most recent `mppopmd` configuration struct.
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(default)]
+pub struct Config {
+ /// Configuration format version-- must be "1"
+ // Workaround to https://github.com/rotty/lexpr-rs/issues/77
+ // When this gets fixed, I can remove this element from the struct & deserialize as
+ // a Configurations element-- the on-disk format will be the same.
+ #[serde(rename = "version")]
+ _version: String,
+
+ /// Location of log file
+ pub log: PathBuf,
+
+ /// How to connect to mpd
+ pub conn: Connection,
+
+ /// The mode to start in
+ pub mode: Mode,
+
+ /// The `mpd' root music directory, relative to the host on which *this* daemon is running
+ pub local_music_dir: PathBuf,
+
+ /// Percentage threshold, expressed as a number between zero & one, for considering a song to
+ /// have been played
+ pub played_thresh: f64,
+
+ /// The interval, in milliseconds, at which to poll `mpd' for the current state
+ pub poll_interval_ms: u64,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self::new().unwrap()
+ }
+}
+
+impl Config {
+ fn new() -> Result<Self> {
+ Ok(Self {
+ _version: String::from("1"),
+ log: [LOCALSTATEDIR, "log", "mppopmd.log"].iter().collect(),
+ conn: Connection::new()?,
+ local_music_dir: [PREFIX, "Music"].iter().collect(),
+ played_thresh: 0.6,
+ poll_interval_ms: 5000,
+ mode: Mode::default(),
+ })
+ }
+}
+
+pub fn from_str(text: &str) -> Result<Config> {
+ let cfg: Config = match serde_json::from_str(text) {
+ Ok(cfg) => cfg,
+ Err(err_outer) => {
+ bail!("Failed to parse config: `{}`", err_outer)
+ }
+ };
+ Ok(cfg)
+}