use std::{
io::{self, Read, Write},
sync::mpsc::Sender,
};
use selene_core::library::collection::Collectable;
use blake3::Hash;
use crate::{
IpcCommand, IpcHandleError, IpcRequest, IpcTx, PacketError, PlayerEvent,
player::{PlayerQueryFlags, QueryResult},
playlist::{LoopMode, PlaybackStatus, ShuffleMode},
};
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)
}
}
}
impl SeleneDaemon {
pub fn ipc_generic(&self, command: IpcCommand) -> Result<(), PacketError> {
self.handle_tx.no_response(command)
}
pub fn ipc_flush(&self) -> Result<(), PacketError> {
self.handle_tx.request(IpcCommand::Flush)
}
pub fn ipc_disconnect(&self) {
self.handle_tx.action(IpcCommand::Disconnect);
}
pub fn ipc_reload_config(&self) -> Result<Result<(), String>, PacketError> {
self.handle_tx.request(IpcCommand::ReloadConfig)
}
pub fn ipc_play(&self, collectable: Vec<Collectable>) {
self.handle_tx.action(IpcCommand::Play {
collectables: collectable,
});
}
pub fn ipc_stop(&self) {
self.handle_tx.action(IpcCommand::Stop);
}
pub fn ipc_set_is_playing(&self, is_playing: bool) {
self.handle_tx
.action(IpcCommand::SetIsPlaying { is_playing });
}
pub fn ipc_toggle_is_playing(&self) -> Result<PlaybackStatus, PacketError> {
self.handle_tx.request(IpcCommand::TogglePlaying)
}
pub fn ipc_seek(&self, time: f64, increment: bool) -> Result<Option<f64>, PacketError> {
self.handle_tx.request(IpcCommand::Seek {
seconds: time,
increment,
})
}
pub fn ipc_set_volume(&self, volume: f32, increment: bool) -> Result<f32, PacketError> {
self.handle_tx
.request(IpcCommand::SetVolume { volume, increment })
}
pub fn ipc_queue_set(
&self,
tracks: Vec<Collectable>,
expected_state: Hash,
) -> Result<bool, PacketError> {
self.handle_tx.request(IpcCommand::QueueSet {
tracks,
expected_state,
})
}
pub fn ipc_queue_extend(&self, tracks: Vec<Collectable>) {
self.handle_tx.action(IpcCommand::QueueExtend(tracks));
}
pub fn ipc_queue_shuffle(&self) {
self.handle_tx.action(IpcCommand::QueueShuffle);
}
pub fn ipc_queue_clear(&self) {
self.handle_tx.action(IpcCommand::QueueClear);
}
pub fn ipc_playlist_set(
&self,
collectables: Vec<Collectable>,
expected_state: Hash,
) -> Result<bool, PacketError> {
self.handle_tx.request(IpcCommand::PlaylistSet {
collectables,
expected_state,
})
}
pub fn ipc_playlist_extend(&self, collectables: Vec<Collectable>) {
self.handle_tx
.action(IpcCommand::PlaylistExtend(collectables));
}
pub fn ipc_playlist_clear(&self) {
self.handle_tx.action(IpcCommand::PlaylistClear);
}
pub fn ipc_playlist_set_shuffle_mode(&self, shuffle_mode: ShuffleMode) {
self.handle_tx
.action(IpcCommand::PlaylistSetShuffleMode { shuffle_mode });
}
pub fn ipc_playlist_set_loop_mode(&self, loop_mode: LoopMode) {
self.handle_tx
.action(IpcCommand::PlaylistSetLoopMode { loop_mode });
}
pub fn ipc_tracklist_rebuild(&self) {
self.handle_tx.action(IpcCommand::TracklistRebuild);
}
pub fn ipc_tracklist_seek(&self, index: isize, increment: bool) -> Result<usize, PacketError> {
self.handle_tx
.request(IpcCommand::TracklistSeek { index, increment })
}
pub fn ipc_query(&self, flags: PlayerQueryFlags) -> Result<QueryResult, PacketError> {
self.handle_tx.request(IpcCommand::GetState { flags })
}
}
impl Drop for SeleneDaemon {
fn drop(&mut self) {
let () = self.handle_tx.disconnect();
}
}
pub trait SeleneDaemonHandle: 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>)
where
F: FnMut(PlayerEvent) + Send + Sync + 'static;
}
fn read_packet_data(stream: &mut impl Read) -> io::Result<Vec<u8>> {
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf)?;
let len = u32::from_be_bytes(len_buf) as usize;
let mut data = vec![0u8; len];
stream.read_exact(&mut data)?;
Ok(data)
}
fn send_command(
stream: &mut impl Write,
command: &IpcCommand,
) -> Result<(), Box<dyn std::error::Error>> {
let mut buf = Vec::new();
ciborium::into_writer(&command, &mut buf).expect("Serialization should not fail.");
stream.write_all(&(buf.len() as u32).to_be_bytes())?;
stream.write_all(&buf)?;
Ok(())
}
#[cfg(unix)]
pub mod unix_socket_handle;