use crate::commands::{FormalParameter, Update};
use crate::vars::{LOCALSTATEDIR, PREFIX};
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Debug)]
pub struct GeneralizedCommandDefn {
pub name: String,
pub formal_parameters: Vec<FormalParameter>,
pub default_after: usize,
pub cmd: PathBuf,
pub args: Vec<String>,
pub update: Update,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Config0 {
log: PathBuf,
host: String,
port: u16,
local_music_dir: PathBuf,
playcount_sticker: String,
lastplayed_sticker: String,
played_thresh: f64,
poll_interval_ms: u64,
commands_chan: String,
playcount_command: String,
playcount_command_args: Vec<String>,
rating_sticker: String,
ratings_command: String,
ratings_command_args: Vec<String>,
gen_cmds: Vec<GeneralizedCommandDefn>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub enum Connection {
Local { path: PathBuf },
TCP { host: String, port: u16 },
}
#[cfg(test)]
mod test_connection {
use super::Connection;
#[test]
fn test_serde() {
use serde_lexpr::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))"#)
);
}
}
impl std::default::Default for Connection {
fn default() -> Self {
Connection::TCP {
host: String::from("localhost"),
port: 6600 as u16,
}
}
}
#[derive(Deserialize, Debug, Serialize)]
#[serde(default)]
pub struct Config {
#[serde(rename = "version")]
_version: String,
pub log: PathBuf,
pub conn: Connection,
pub local_music_dir: PathBuf,
pub playcount_sticker: String,
pub lastplayed_sticker: String,
pub played_thresh: f64,
pub poll_interval_ms: u64,
pub commands_chan: String,
pub playcount_command: String,
pub playcount_command_args: Vec<String>,
pub rating_sticker: String,
pub ratings_command: String,
pub ratings_command_args: Vec<String>,
pub gen_cmds: Vec<GeneralizedCommandDefn>,
}
impl Default for Config {
fn default() -> Config {
Config {
_version: String::from("1"),
log: [LOCALSTATEDIR, "log", "mppopmd.log"].iter().collect(),
conn: Connection::default(),
local_music_dir: [PREFIX, "Music"].iter().collect(),
playcount_sticker: String::from("unwoundstack.com:playcount"),
lastplayed_sticker: String::from("unwoundstack.com:lastplayed"),
played_thresh: 0.6,
poll_interval_ms: 5000,
commands_chan: String::from("unwoundstack.com:commands"),
playcount_command: String::new(),
playcount_command_args: Vec::<String>::new(),
rating_sticker: String::from("unwoundstack.com:rating"),
ratings_command: String::new(),
ratings_command_args: Vec::<String>::new(),
gen_cmds: Vec::<GeneralizedCommandDefn>::new(),
}
}
}
impl From<Config0> for Config {
fn from(cfg0: Config0) -> Self {
Config {
_version: String::from("1"),
log: cfg0.log,
conn: Connection::TCP {
host: cfg0.host,
port: cfg0.port,
},
local_music_dir: cfg0.local_music_dir,
playcount_sticker: cfg0.playcount_sticker,
lastplayed_sticker: cfg0.lastplayed_sticker,
played_thresh: cfg0.played_thresh,
poll_interval_ms: cfg0.poll_interval_ms,
commands_chan: cfg0.commands_chan,
playcount_command: cfg0.playcount_command,
playcount_command_args: cfg0.playcount_command_args,
rating_sticker: cfg0.rating_sticker,
ratings_command: cfg0.ratings_command,
ratings_command_args: cfg0.ratings_command_args,
gen_cmds: cfg0.gen_cmds,
}
}
}
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Failed to parse configuration: `{}', `{}'", outer, inner))]
ParseFail {
outer: Box<dyn std::error::Error>,
inner: Box<dyn std::error::Error>,
},
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn from_str(text: &str) -> Result<Config> {
let cfg: Config = match serde_lexpr::from_str(&text) {
Ok(cfg) => cfg,
Err(err_outer) => {
let fallback: serde_lexpr::Result<Config0> = serde_lexpr::from_str(&text);
match fallback {
Ok(cfg) => Config::from(cfg),
Err(err_inner) => {
return Err(Error::ParseFail {
outer: Box::new(err_outer),
inner: Box::new(err_inner),
})
}
}
}
};
Ok(cfg)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_from_str() {
let cfg = Config::default();
assert_eq!(cfg.commands_chan, String::from("unwoundstack.com:commands"));
assert_eq!(
serde_lexpr::to_string(&cfg).unwrap(),
format!(
r#"((version . "1") (log . "{}/log/mppopmd.log") (conn TCP (host . "localhost") (port . 6600)) (local_music_dir . "{}/Music") (playcount_sticker . "unwoundstack.com:playcount") (lastplayed_sticker . "unwoundstack.com:lastplayed") (played_thresh . 0.6) (poll_interval_ms . 5000) (commands_chan . "unwoundstack.com:commands") (playcount_command . "") (playcount_command_args) (rating_sticker . "unwoundstack.com:rating") (ratings_command . "") (ratings_command_args) (gen_cmds))"#,
LOCALSTATEDIR, PREFIX
)
);
let cfg: Config = serde_lexpr::from_str(
r#"
((version . "1")
(log . "/usr/local/var/log/mppopmd.log")
(conn TCP (host . "localhost") (port . 6600))
(local_music_dir . "/usr/local/Music")
(playcount_sticker . "unwoundstack.com:playcount")
(lastplayed_sticker . "unwoundstack.com:lastplayed")
(played_thresh . 0.6)
(poll_interval_ms . 5000)
(commands_chan . "unwoundstack.com:commands")
(playcount_command . "")
(playcount_command_args)
(rating_sticker . "unwoundstack.com:rating")
(ratings_command . "")
(ratings_command_args)
(gen_cmds))
"#,
)
.unwrap();
assert_eq!(cfg._version, String::from("1"));
let cfg: Config = serde_lexpr::from_str(
r#"
((version . "1")
(log . "/usr/local/var/log/mppopmd.log")
(conn Local (path . "/home/mgh/var/run/mpd/mpd.sock"))
(local_music_dir . "/usr/local/Music")
(playcount_sticker . "unwoundstack.com:playcount")
(lastplayed_sticker . "unwoundstack.com:lastplayed")
(played_thresh . 0.6)
(poll_interval_ms . 5000)
(commands_chan . "unwoundstack.com:commands")
(playcount_command . "")
(playcount_command_args)
(rating_sticker . "unwoundstack.com:rating")
(ratings_command . "")
(ratings_command_args)
(gen_cmds))
"#,
)
.unwrap();
assert_eq!(cfg._version, String::from("1"));
assert_eq!(
cfg.conn,
Connection::Local {
path: PathBuf::from("/home/mgh/var/run/mpd/mpd.sock")
}
);
let cfg = from_str(r#"
((log . "/home/mgh/var/log/mppopmd.log")
(host . "192.168.1.14")
(port . 6600)
(local_music_dir . "/space/mp3")
(playcount_sticker . "unwoundstack.com:playcount")
(lastplayed_sticker . "unwoundstack.com:lastplayed")
(played_thresh . 0.6)
(poll_interval_ms . 5000)
(playcount_command . "/usr/local/bin/scribbu")
(playcount_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file"))
(commands_chan . "unwoundstack.com:commands")
(rating_sticker . "unwoundstack.com:rating")
(ratings_command . "/usr/local/bin/scribbu")
(ratings_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-r" "%rating" "%full-file"))
(gen_cmds .
(((name . "set-genre")
(formal_parameters . (Literal Track))
(default_after . 1)
(cmd . "/usr/local/bin/scribbu")
(args . ("genre" "-a" "-C" "-g" "%1" "%full-file"))
(update . TrackOnly))
((name . "set-xtag")
(formal_parameters . (Literal Track))
(default_after . 1)
(cmd . "/usr/local/bin/scribbu")
(args . ("xtag" "-A" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
(update . TrackOnly))
((name . "merge-xtag")
(formal_parameters . (Literal Track))
(default_after . 1)
(cmd . "/usr/local/bin/scribbu")
(args . ("xtag" "-m" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
(update . TrackOnly)))))
"#).unwrap();
assert_eq!(cfg.log, PathBuf::from("/home/mgh/var/log/mppopmd.log"));
}
}