#[cfg(feature = "album-art")]
use crate::art::ArtFetcher;
use crate::mpris::MprisPropertiesChange;
use crate::mpris::PlayerMetadata;
use crate::mpris::PlayerStatus;
use crate::notifier::Notification;
use crate::DBusError;
use crate::{configuration::Configuration, dbus::DBusConnection, notifier::Notifier};
use rustbus::message_builder::MarshalledMessage;
use std::collections::HashMap;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
use thiserror::Error;
const NOTIFICATION_DELAY: Duration = Duration::from_millis(250);
#[derive(Debug, Error)]
pub enum SignalHandlerError {
#[error("error handling D-Bus signal")]
DBus(#[from] DBusError),
}
pub struct SignalHandler {
configuration: Configuration,
notifier: Notifier,
art_fetcher: ArtFetcher,
metadata: HashMap<String, PlayerMetadata>,
status: HashMap<String, PlayerStatus>,
pending_notification: Option<Notification>,
pending_commands: Vec<Command>,
}
impl SignalHandler {
pub fn new(configuration: &Configuration) -> Self {
Self {
configuration: configuration.clone(),
notifier: Notifier::new(configuration),
art_fetcher: ArtFetcher::new(configuration),
metadata: HashMap::new(),
pending_notification: None,
pending_commands: Vec::new(),
status: HashMap::new(),
}
}
pub fn handle_pending(&mut self, dbus: &mut DBusConnection) -> Result<(), SignalHandlerError> {
if let Some(pending) = &self.pending_notification {
let delta = Instant::now() - pending.last_touched();
if delta > NOTIFICATION_DELAY {
self.notifier
.send_notification(self.pending_notification.take().unwrap(), dbus)?;
for command in self.pending_commands.iter_mut() {
match command.output() {
Ok(_) => (),
Err(err) => {
log::warn!("Command failed: {}", err);
}
}
}
self.pending_commands.clear();
}
}
Ok(())
}
pub fn handle_signal(&mut self, signal: MarshalledMessage) -> Result<(), SignalHandlerError> {
let sender = signal
.dynheader
.sender
.as_ref()
.ok_or_else(|| DBusError::Invalid("Missing sender header".to_string()))?
.clone();
let change = MprisPropertiesChange::try_from(signal).ok();
if change.is_none() {
return Ok(());
}
if let Some(commands) = self.configuration.commands.as_ref() {
self.pending_commands = commands
.iter()
.filter_map(|command_args| match command_args.len() {
0 => None,
1 => Some(Command::new(command_args[0].as_str())),
2.. => {
let mut cmd = Command::new(command_args[0].as_str());
cmd.args(&command_args[1..command_args.len()]);
Some(cmd)
}
})
.collect();
}
let change = change.unwrap();
let mut metadata: Option<&PlayerMetadata> = self.metadata.get(&sender);
if let Some(new_metadata) = change.metadata {
self.metadata
.insert(sender.to_string(), new_metadata.clone());
metadata = self.metadata.get(&sender);
self.status.remove(&sender);
let pending = self.pending_notification.as_mut();
if let Some(pending) = pending {
if pending.sender() == sender {
pending.update(&new_metadata, None);
}
} else {
self.pending_notification = Some(Notification::new(&sender, &new_metadata, None));
}
}
if metadata.is_none() {
return Ok(());
}
let metadata = metadata.unwrap();
if let Some(status) = change.status {
let last_status = self.status.insert(sender.clone(), status.clone());
if status == PlayerStatus::Playing {
if last_status.is_none() || last_status.is_some_and(|l| l != PlayerStatus::Playing)
{
self.pending_notification = Some(Notification::new(&sender, metadata, None));
}
} else {
self.pending_notification = None;
}
}
if self.pending_notification.as_mut().is_none() {
return Ok(());
}
let pending = self.pending_notification.as_mut().unwrap();
#[cfg(feature = "album-art")]
if metadata.art_url.is_some() && self.configuration.enable_album_art {
let result = self
.art_fetcher
.get_album_art(metadata.art_url.as_ref().unwrap());
match result {
Ok(data) => {
pending.update(metadata, Some(data));
}
Err(err) => {
log::warn!("Error fetching album art for {:#?}: {}", &metadata, err);
}
}
}
Ok(())
}
}