Skip to main content

selene_daemon/
daemon.rs

1use std::{
2    io::{self, Read, Write},
3    sync::mpsc::Sender,
4};
5
6use selene_core::library::collection::Collectable;
7
8use blake3::Hash;
9
10use crate::{
11    IpcCommand, IpcHandleError, IpcRequest, IpcTx, PacketError, PlayerEvent,
12    player::{PlayerQueryFlags, QueryResult},
13    playlist::{LoopMode, PlaybackStatus, ShuffleMode},
14};
15
16// Interface stuff
17/// Daemon connection abstraction
18///
19/// Allow clients to connect to selene-daemon using one of the handles compatible with their system. Defines wrapper functions around all IPC calls to allow for easy get/set calls
20pub struct SeleneDaemon {
21    handle_tx: Sender<IpcRequest>,
22}
23
24// Daemon impls
25impl SeleneDaemon {
26    /// Create a new connection to the daemon
27    ///
28    /// # Errors
29    ///
30    /// This function will error if:
31    /// - There is no [`DaemonHandle`] configured for the current system
32    /// - The connection could not be established (May be because the daemon is not running)
33    pub fn connect<F>(callback: Option<F>) -> Result<SeleneDaemon, IpcHandleError>
34    where
35        Self: Sized,
36        F: FnMut(PlayerEvent) + Send + Sync + 'static,
37    {
38        #[cfg(unix)]
39        {
40            Ok(Self {
41                handle_tx: unix_socket_handle::UnixSocketHandle::connect(callback)?,
42            })
43        }
44
45        #[cfg(not(any(unix)))]
46        {
47            Err(IpcHandleError::UnsupportedPlatform)
48        }
49    }
50}
51
52impl SeleneDaemon {
53    // Generic
54    pub fn ipc_generic(&self, command: IpcCommand) -> Result<(), PacketError> {
55        self.handle_tx.no_response(command)
56    }
57
58    pub fn ipc_flush(&self) -> Result<(), PacketError> {
59        self.handle_tx.request(IpcCommand::Flush)
60    }
61
62    pub fn ipc_disconnect(&self) {
63        self.handle_tx.action(IpcCommand::Disconnect);
64    }
65
66    pub fn ipc_reload_config(&self) -> Result<Result<(), String>, PacketError> {
67        self.handle_tx.request(IpcCommand::ReloadConfig)
68    }
69
70    // Playback
71    pub fn ipc_play(&self, collectable: Collectable) {
72        self.handle_tx.action(IpcCommand::Play { collectable });
73    }
74
75    pub fn ipc_stop(&self) {
76        self.handle_tx.action(IpcCommand::Stop);
77    }
78
79    pub fn ipc_set_is_playing(&self, is_playing: bool) {
80        self.handle_tx
81            .action(IpcCommand::SetIsPlaying { is_playing });
82    }
83
84    pub fn ipc_toggle_is_playing(&self) -> Result<PlaybackStatus, PacketError> {
85        self.handle_tx.request(IpcCommand::TogglePlaying)
86    }
87
88    pub fn ipc_seek(&self, time: f64, increment: bool) -> Result<Option<f64>, PacketError> {
89        self.handle_tx.request(IpcCommand::Seek {
90            seconds: time,
91            increment,
92        })
93    }
94
95    pub fn ipc_set_volume(&self, volume: f32, increment: bool) -> Result<f32, PacketError> {
96        self.handle_tx
97            .request(IpcCommand::SetVolume { volume, increment })
98    }
99
100    // Queue
101    pub fn ipc_queue_set(
102        &self,
103        tracks: Vec<Collectable>,
104        expected_state: Hash,
105    ) -> Result<bool, PacketError> {
106        self.handle_tx.request(IpcCommand::QueueSet {
107            tracks,
108            expected_state,
109        })
110    }
111
112    pub fn ipc_queue_extend(&self, tracks: Vec<Collectable>) {
113        self.handle_tx.action(IpcCommand::QueueExtend(tracks));
114    }
115
116    pub fn ipc_queue_shuffle(&self) {
117        self.handle_tx.action(IpcCommand::QueueShuffle);
118    }
119
120    pub fn ipc_queue_clear(&self) {
121        self.handle_tx.action(IpcCommand::QueueClear);
122    }
123
124    // Playlist
125    pub fn ipc_playlist_set(
126        &self,
127        collectables: Vec<Collectable>,
128        expected_state: Hash,
129    ) -> Result<bool, PacketError> {
130        self.handle_tx.request(IpcCommand::PlaylistSet {
131            collectables,
132            expected_state,
133        })
134    }
135
136    pub fn ipc_playlist_extend(&self, collectables: Vec<Collectable>) {
137        self.handle_tx
138            .action(IpcCommand::PlaylistExtend(collectables));
139    }
140
141    pub fn ipc_playlist_clear(&self) {
142        self.handle_tx.action(IpcCommand::PlaylistClear);
143    }
144
145    pub fn ipc_playlist_set_shuffle_mode(&self, shuffle_mode: ShuffleMode) {
146        self.handle_tx
147            .action(IpcCommand::PlaylistSetShuffleMode { shuffle_mode });
148    }
149
150    pub fn ipc_playlist_set_loop_mode(&self, loop_mode: LoopMode) {
151        self.handle_tx
152            .action(IpcCommand::PlaylistSetLoopMode { loop_mode });
153    }
154
155    // Tracklist
156    pub fn ipc_tracklist_rebuild(&self) {
157        self.handle_tx.action(IpcCommand::TracklistRebuild);
158    }
159
160    pub fn ipc_tracklist_seek(&self, index: isize, increment: bool) -> Result<usize, PacketError> {
161        self.handle_tx
162            .request(IpcCommand::TracklistSeek { index, increment })
163    }
164
165    // Queries
166    pub fn ipc_query(&self, flags: PlayerQueryFlags) -> Result<QueryResult, PacketError> {
167        self.handle_tx.request(IpcCommand::GetState { flags })
168    }
169}
170
171impl Drop for SeleneDaemon {
172    fn drop(&mut self) {
173        let () = self.handle_tx.disconnect();
174    }
175}
176
177/// Daemon connection abstraction. Clients only need to be concerned with sending an IPC, and getting a response back, not how they are connecting
178pub trait SeleneDaemonHandle: Send {
179    /// Connects to the daemon and runs the handle in its own thread
180    fn connect<F>(callback: Option<F>) -> Result<Sender<IpcRequest>, IpcHandleError>
181    where
182        Self: Sized,
183        F: FnMut(PlayerEvent) + Send + Sync + 'static;
184
185    /// Attempts to reconnect to the daemon
186    fn reconnect(&mut self) -> bool;
187
188    /// Main handle logic. This should run forever and in its own thread
189    fn run<F>(self, callback: Option<F>)
190    where
191        F: FnMut(PlayerEvent) + Send + Sync + 'static;
192}
193
194/// Assumes the next two byte groups are \[len\]\[data\] and that the packet type has already been read
195///
196/// Returns just the packet data from the buffer
197///
198/// # Errors
199///
200/// Errors if [`read_exact()`] fails
201fn read_packet_data(stream: &mut impl Read) -> io::Result<Vec<u8>> {
202    let mut len_buf = [0u8; 4];
203    stream.read_exact(&mut len_buf)?;
204    let len = u32::from_be_bytes(len_buf) as usize;
205
206    let mut data = vec![0u8; len];
207    stream.read_exact(&mut data)?;
208
209    Ok(data)
210}
211
212fn send_command(
213    stream: &mut impl Write,
214    command: &IpcCommand,
215) -> Result<(), Box<dyn std::error::Error>> {
216    let mut buf = Vec::new();
217    ciborium::into_writer(&command, &mut buf).expect("Serialization should not fail.");
218    stream.write_all(&(buf.len() as u32).to_be_bytes())?;
219    stream.write_all(&buf)?;
220    Ok(())
221}
222
223#[cfg(unix)]
224/// `DaemonHandle` implementation for unix using unix sockets
225pub mod unix_socket_handle;