use std::sync::mpsc::{RecvError, SendError, Sender, channel};
use blake3::Hash;
use rubato::ResamplerConstructionError;
use symphonia::core::errors::Error as SymphoniaError;
use thiserror::Error;
use crate::{
IpcActionError, IpcCommand, IpcHandleError, PlayerEvent, PlayerResponse,
player::playback::CpalError,
playlist::{LoopMode, Playable, PlayableTrack, ShuffleMode, TracklistTrack},
};
#[derive(Debug, Error)]
pub enum DaemonError {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
FailedToSerialize(#[from] ciborium::ser::Error<std::io::Error>),
#[error("A MPSC sender closed unexpectedly")]
ReceiverDisconnected,
#[error("A MPSC receiever closed unexpectedly")]
SenderDisconnected,
#[error("{0}")]
Cpal(#[from] CpalError),
#[error("{0}")]
Symphonia(#[from] SymphoniaError),
#[error("{0}")]
Resampler(#[from] ResamplerConstructionError),
}
impl From<RecvError> for DaemonError {
fn from(_value: RecvError) -> Self {
Self::ReceiverDisconnected
}
}
impl<T> From<SendError<T>> for DaemonError {
fn from(_value: SendError<T>) -> Self {
Self::SenderDisconnected
}
}
pub struct SeleneDaemon {
handle_tx: Sender<IpcRequest>,
}
impl SeleneDaemon {
pub fn connect<F>(callback: Option<F>) -> Result<SeleneDaemon, IpcHandleError>
where
Self: Sized,
F: FnMut(PlayerEvent) + Send + Sync + 'static,
{
#[cfg(unix)]
{
Ok(Self {
handle_tx: unix_socket_handle::UnixSocketHandle::connect(callback)?,
})
}
#[cfg(not(any(unix)))]
{
Err(IpcHandleError::UnsupportedPlatform)
}
}
fn send_ipc(&self, command: IpcCommand) -> Result<PlayerResponse, IpcActionError> {
self.handle_tx.request(command)
}
fn send_ipc_action(&self, command: IpcCommand) -> Result<(), IpcActionError> {
match self.send_ipc(command) {
Ok(PlayerResponse::Done) => Ok(()),
Err(err) => Err(err),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
fn send_ipc_action_infallible(&self, command: IpcCommand) -> Result<(), IpcActionError> {
match self.send_ipc(command) {
Ok(PlayerResponse::Done) => Ok(()),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn disconnect(&self) {
let _ = self.handle_tx.request(IpcCommand::Disconnect);
}
}
impl SeleneDaemon {
pub fn ipc_flush(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::Flush)
}
pub fn ipc_play(&self, playable: Playable) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::Play { playable })
}
pub fn ipc_stop(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::Stop)
}
pub fn ipc_set_playing(&self, playing: bool) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::SetPlaying(playing))
}
pub fn ipc_toggle_playing(&self) -> Result<bool, IpcActionError> {
match self.send_ipc(IpcCommand::TogglePlaying) {
Ok(PlayerResponse::IsPlaying(playing)) => Ok(playing),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_get_playing(&self) -> Result<bool, IpcActionError> {
match self.send_ipc(IpcCommand::GetPlaying) {
Ok(PlayerResponse::IsPlaying(playing)) => Ok(playing),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_seek(&self, seconds: f64, increment: bool) -> Result<Option<f64>, IpcActionError> {
match self.send_ipc(IpcCommand::Seek { seconds, increment }) {
Ok(PlayerResponse::Time(time)) => Ok(time),
Err(IpcActionError::NothingPlaying) => Err(IpcActionError::NothingPlaying),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_set_volume(&self, volume: f32, increment: bool) -> Result<f32, IpcActionError> {
match self.send_ipc(IpcCommand::SetVolume { volume, increment }) {
Ok(PlayerResponse::Volume(vol)) => Ok(vol),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_queue_extend(
&self,
playables: impl IntoIterator<Item = Playable>,
) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::QueueExtend(playables.into_iter().collect()))
}
pub fn ipc_queue_set(
&self,
expected_state: Hash,
tracks: impl IntoIterator<Item = PlayableTrack>,
) -> Result<(), IpcActionError> {
self.send_ipc_action(IpcCommand::QueueSet {
tracks: tracks.into_iter().collect(),
expected_state,
})
}
pub fn ipc_queue_get(&self) -> Result<Vec<PlayableTrack>, IpcActionError> {
match self.send_ipc(IpcCommand::QueueGet) {
Ok(PlayerResponse::Queue(queue)) => Ok(queue),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_queue_shuffle(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::QueueShuffle)
}
pub fn ipc_queue_clear(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::QueueClear)
}
pub fn ipc_playlist_clear(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::PlaylistClear)
}
pub fn ipc_playlist_extend(
&self,
playables: impl IntoIterator<Item = Playable>,
) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::PlaylistExtend(playables.into_iter().collect()))
}
pub fn ipc_playlist_set(
&self,
expected_state: Hash,
playables: impl IntoIterator<Item = Playable>,
) -> Result<(), IpcActionError> {
self.send_ipc_action(IpcCommand::PlaylistSet {
playables: playables.into_iter().collect(),
expected_state,
})
}
pub fn ipc_tracklist_seek(
&self,
index: isize,
increment: bool,
) -> Result<(usize, usize), IpcActionError> {
match self.send_ipc(IpcCommand::TracklistSeek { index, increment }) {
Ok(PlayerResponse::TracklistPosition { index, max }) => Ok((index, max)),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_playlist_get(&self) -> Result<Vec<Playable>, IpcActionError> {
match self.send_ipc(IpcCommand::PlaylistGet) {
Ok(PlayerResponse::Playlist(playlist)) => Ok(playlist),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_set_shuffle_mode(&self, shuffle_mode: ShuffleMode) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::PlaylistSetShuffleMode { shuffle_mode })
}
pub fn ipc_get_shuffle_mode(&self) -> Result<ShuffleMode, IpcActionError> {
match self.send_ipc(IpcCommand::PlaylistGetShuffleMode) {
Ok(PlayerResponse::ShuffleMode(shuffle_mode)) => Ok(shuffle_mode),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_set_loop_mode(&self, loop_mode: LoopMode) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::PlaylistSetLoopMode { loop_mode })
}
pub fn ipc_get_loop_mode(&self) -> Result<LoopMode, IpcActionError> {
match self.send_ipc(IpcCommand::PlaylistGetLoopMode) {
Ok(PlayerResponse::LoopMode(loop_mode)) => Ok(loop_mode),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_tracklist_rebuild(&self) -> Result<(), IpcActionError> {
self.send_ipc_action_infallible(IpcCommand::TracklistRebuild)
}
pub fn ipc_tracklist_get(&self) -> Result<Vec<TracklistTrack>, IpcActionError> {
match self.send_ipc(IpcCommand::TracklistGet) {
Ok(PlayerResponse::Tracklist(tracklist)) => Ok(tracklist),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_queue_get_state(&self) -> Result<Hash, IpcActionError> {
match self.send_ipc(IpcCommand::QueueGetState) {
Ok(PlayerResponse::QueueState(state)) => Ok(state),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
pub fn ipc_playlist_get_state(&self) -> Result<Hash, IpcActionError> {
match self.send_ipc(IpcCommand::PlaylistGetState) {
Ok(PlayerResponse::PlaylistState(state)) => Ok(state),
Err(IpcActionError::Disconnected) => Err(IpcActionError::Disconnected),
response => panic!("Ipc action received unexpected response: {response:?}"),
}
}
}
impl Drop for SeleneDaemon {
fn drop(&mut self) {
self.disconnect()
}
}
pub enum IpcRequest {
Ipc {
command: Box<IpcCommand>,
callback: Sender<Result<PlayerResponse, IpcActionError>>,
},
Reconnect {
callback: Sender<bool>,
},
}
pub trait IpcTx {
fn request(&self, command: IpcCommand) -> Result<PlayerResponse, IpcActionError>;
fn reconnect(&self) -> Result<bool, IpcHandleError>;
}
impl IpcTx for Sender<IpcRequest> {
fn request(&self, command: IpcCommand) -> Result<PlayerResponse, IpcActionError> {
let (tx, rx) = channel();
self.send(IpcRequest::Ipc {
command: Box::new(command),
callback: tx,
})
.map_err(|_| IpcActionError::Disconnected)?;
rx.recv()
.map_err(|_| IpcActionError::Disconnected)
.flatten()
}
fn reconnect(&self) -> Result<bool, IpcHandleError> {
let (tx, rx) = channel();
self.send(IpcRequest::Reconnect { callback: tx })
.map_err(|_| IpcHandleError::HandleDied)?;
rx.recv().map_err(|_| IpcHandleError::HandleDied)
}
}
pub trait DaemonHandle<Command, Response>: Send {
fn connect<F>(callback: Option<F>) -> Result<Sender<IpcRequest>, IpcHandleError>
where
Self: Sized,
F: FnMut(PlayerEvent) + Send + Sync + 'static;
fn reconnect(&mut self) -> bool;
fn run<F>(self, callback: Option<F>) -> Result<(), IpcHandleError>
where
F: FnMut(PlayerEvent) + Send + Sync + 'static;
}
#[cfg(unix)]
pub mod unix_socket_handle;