diff options
Diffstat (limited to 'pkgs/by-name/mp/mpdpopm/src/lib.rs')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/lib.rs | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs new file mode 100644 index 00000000..d5db57b4 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs @@ -0,0 +1,163 @@ +// 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/>. + +//! # mpdpopm +//! +//! 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)). +//! +//! # Commands +//! +//! I'm currently sending all commands over one (configurable) channel. +//! + +#![recursion_limit = "512"] // for the `select!' macro + +pub mod clients; +pub mod config; +pub mod filters_ast; +pub mod playcounts; +pub mod storage; +pub mod vars; + +#[rustfmt::skip] +#[allow(clippy::extra_unused_lifetimes)] +#[allow(clippy::needless_lifetimes)] +#[allow(clippy::let_unit_value)] +#[allow(clippy::just_underscores_and_digits)] +pub mod filters { + include!(concat!(env!("OUT_DIR"), "/src/filters.rs")); +} + +use crate::{ + clients::{Client, IdleClient, IdleSubSystem}, + config::{Config, Connection}, + playcounts::PlayState, +}; + +use anyhow::{Context, Error}; +use futures::{future::FutureExt, pin_mut, select}; +use tokio::{ + signal, + signal::unix::{SignalKind, signal}, + time::{Duration, sleep}, +}; +use tracing::{debug, error, info}; + +/// Core `mppopmd' logic +pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> { + info!("mpdpopm {} beginning.", vars::VERSION); + + let mut client = + match cfg.conn { + Connection::Local { ref path } => Client::open(path) + .await + .with_context(|| format!("Failed to open socket at `{}`", path.display()))?, + Connection::TCP { ref host, port } => Client::connect(format!("{}:{}", host, port)) + .await + .with_context(|| format!("Failed to connect to client at `{}:{}`", host, port))?, + }; + + let mut state = PlayState::new(&mut client, cfg.played_thresh) + .await + .context("Failed to construct PlayState")?; + + let mut idle_client = match cfg.conn { + Connection::Local { ref path } => IdleClient::open(path) + .await + .context("Failed to open idle client")?, + Connection::TCP { ref host, port } => IdleClient::connect(format!("{}:{}", host, port)) + .await + .context("Failed to connect to TCP idle client")?, + }; + + idle_client + .subscribe(&cfg.commands_chan) + .await + .context("Failed to subscribe to idle_client")?; + + let mut hup = signal(SignalKind::hangup()).unwrap(); + let mut kill = signal(SignalKind::terminate()).unwrap(); + let ctrl_c = signal::ctrl_c().fuse(); + + let sighup = hup.recv().fuse(); + let sigkill = kill.recv().fuse(); + + let tick = sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse(); + pin_mut!(ctrl_c, sighup, sigkill, tick); + + let mut done = false; + while !done { + debug!("selecting..."); + { + // `idle_client' mutably borrowed here + let mut idle = Box::pin(idle_client.idle().fuse()); + loop { + select! { + _ = ctrl_c => { + info!("got ctrl-C"); + done = true; + break; + }, + _ = sighup => { + info!("got SIGHUP"); + done = true; + break; + }, + _ = sigkill => { + info!("got SIGKILL"); + done = true; + break; + }, + _ = tick => { + tick.set(sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse()); + state.update(&mut client) + .await + .context("PlayState update failed")? + }, + res = idle => match res { + Ok(subsys) => { + debug!("subsystem {} changed", subsys); + if subsys == IdleSubSystem::Player { + state.update(&mut client) + .await + .context("PlayState update failed")? + } else if subsys == IdleSubSystem::Message { + error!("Message handling is not supported!"); + } + break; + }, + Err(err) => { + debug!("error {err:#?} on idle"); + done = true; + break; + } + } + } + } + } + } + + info!("mpdpopm exiting."); + + Ok(()) +} |
