about summary refs log tree commit diff stats
path: root/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs150
1 files changed, 150 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..643611d6
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs
@@ -0,0 +1,150 @@
+// 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::{self, Config},
+    mpdpopm,
+};
+
+use anyhow::{Context, Result, bail};
+use clap::Parser;
+use tracing::{info, level_filters::LevelFilter};
+use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt};
+
+use std::{io, path::PathBuf, sync::MutexGuard};
+
+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 `mpdopmd'.
+///
+/// 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).with_context(|| {
+                format!("Failed to parse config file at: `{}`", cfgpath.display())
+            })?,
+            // 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:
+                bail!(
+                    "No config file could be read at: `{}`, because: {err}",
+                    cfgpath.display()
+                )
+            }
+        }
+    } 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()
+        .context("Failed to construct env filter")?;
+
+    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)).context("Main mpdpopm failed")
+}