diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-24 23:51:04 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-24 23:51:19 +0100 |
| commit | 38c6f95c94830ed8eb6c6678e303480257cf3cf5 (patch) | |
| tree | e5bd55a6306c5f787ff49d528810c62b5c5dbb32 /pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs | |
| parent | module/mpd: Set-up a sticker file (diff) | |
| download | nixos-config-38c6f95c94830ed8eb6c6678e303480257cf3cf5.zip | |
pkgs/mpdpopm: Init
This is based on https://github.com/sp1ff/mpdpopm at commit 178df8ad3a5c39281cfd8b3cec05394f4c9256fd.
Diffstat (limited to '')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs new file mode 100644 index 00000000..e903774c --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs @@ -0,0 +1,233 @@ +// Copyright (C) 2020-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/>. + +//! # mppopmd +//! +//! Maintain ratings & playcounts for your mpd server. +//! +//! # Introduction +//! +//! This is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts & +//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust +//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as +//! the sticker database, by invoking external commands to keep your tags up-to-date (something +//! along the lines of [mpdcron](https://alip.github.io/mpdcron)). + +use mpdpopm::config; +use mpdpopm::config::Config; +use mpdpopm::mpdpopm; + +use backtrace::Backtrace; +use clap::Parser; +use tracing::{info, level_filters::LevelFilter}; +use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt}; + +use std::{fmt, io, path::PathBuf, sync::MutexGuard}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// mppopmd application Error type // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[non_exhaustive] +pub enum Error { + NoConfigArg, + NoConfig { + config: std::path::PathBuf, + cause: std::io::Error, + }, + Filter { + source: tracing_subscriber::filter::FromEnvError, + back: Backtrace, + }, + Fork { + errno: errno::Errno, + back: Backtrace, + }, + PathContainsNull { + back: Backtrace, + }, + OpenLockFile { + errno: errno::Errno, + back: Backtrace, + }, + LockFile { + errno: errno::Errno, + back: Backtrace, + }, + WritePid { + errno: errno::Errno, + back: Backtrace, + }, + Config { + source: crate::config::Error, + back: Backtrace, + }, + MpdPopm { + source: Box<mpdpopm::Error>, + back: Backtrace, + }, +} + +impl std::fmt::Display for Error { + #[allow(unreachable_patterns)] // the _ arm is *currently* unreachable + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::NoConfigArg => write!(f, "No configuration file given"), + Error::NoConfig { config, cause } => { + write!(f, "Configuration error ({:?}): {}", config, cause) + } + Error::Fork { errno, back: _ } => write!(f, "When forking, got errno {}", errno), + Error::PathContainsNull { back: _ } => write!(f, "Path contains a null character"), + Error::OpenLockFile { errno, back: _ } => { + write!(f, "While opening lock file, got errno {}", errno) + } + Error::LockFile { errno, back: _ } => { + write!(f, "While locking the lock file, got errno {}", errno) + } + Error::WritePid { errno, back: _ } => { + write!(f, "While writing pid file, got errno {}", errno) + } + Error::Config { source, back: _ } => write!(f, "Configuration error: {}", source), + Error::MpdPopm { source, back: _ } => write!(f, "mpdpopm error: {}", source), + _ => write!(f, "Unknown mppopmd error"), + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +type Result = std::result::Result<(), Error>; + +pub struct MyMutexGuardWriter<'a>(MutexGuard<'a, std::fs::File>); + +impl io::Write for MyMutexGuardWriter<'_> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + + #[inline] + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { + self.0.write_vectored(bufs) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +/// mpd + POPM +/// +/// `mppopmd' is a companion daemon for `mpd' that maintains playcounts & ratings, +/// as well as implementing some handy functions. It maintains ratings & playcounts in the sticker +/// database, but it allows you to keep that information in your tags, as well, by invoking external +/// commands to keep your tags up-to-date. +#[derive(Parser)] +struct Args { + /// path to configuration file + #[arg(short, long)] + config: Option<PathBuf>, + + /// enable verbose logging + #[arg(short, long)] + verbose: bool, + + /// enable debug loggin (implies --verbose) + #[arg(short, long)] + debug: bool, +} + +/// Entry point for `mppopmd'. +/// +/// Do *not* use the #[tokio::main] attribute here! If this program is asked to daemonize (the usual +/// case), we will fork after tokio has started its thread pool, with disastrous consequences. +/// Instead, stay synchronous until we've daemonized (or figured out that we don't need to), and +/// only then fire-up the tokio runtime. +fn main() -> Result { + use mpdpopm::vars::VERSION; + + let args = Args::parse(); + + let config = if let Some(cfgpath) = &args.config { + match std::fs::read_to_string(cfgpath) { + Ok(text) => config::from_str(&text).map_err(|err| Error::Config { + source: err, + back: Backtrace::new(), + })?, + // The config file (defaulted or not) either didn't exist, or we were unable to read its + // contents... + Err(err) => { + // Either they did _not_, in which case they probably want to know that the config + // file they explicitly asked for does not exist, or there was some other problem, + // in which case we're out of options, anyway. Either way: + return Err(Error::NoConfig { + config: PathBuf::from(cfgpath), + cause: err, + }); + } + } + } else { + Config::default() + }; + + // `--verbose' & `--debug' work as follows: if `--debug' is present, log at level Trace, no + // matter what. Else, if `--verbose' is present, log at level Debug. Else, log at level Info. + let lf = match (args.verbose, args.debug) { + (_, true) => LevelFilter::TRACE, + (true, false) => LevelFilter::DEBUG, + _ => LevelFilter::INFO, + }; + + let filter = EnvFilter::builder() + .with_default_directive(lf.into()) + .from_env() + .map_err(|err| Error::Filter { + source: err, + back: Backtrace::new(), + })?; + + let formatter: Box<dyn Layer<Registry> + Send + Sync> = { + Box::new( + tracing_subscriber::fmt::Layer::default() + .compact() + .with_writer(io::stdout), + ) + }; + + tracing::subscriber::set_global_default(Registry::default().with(formatter).with(filter)) + .unwrap(); + + info!("mppopmd {VERSION} logging at level {lf:#?}."); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(mpdpopm(config)).map_err(|err| Error::MpdPopm { + source: Box::new(err), + back: Backtrace::new(), + }) +} |
