Skip to main content

selene_daemon/client/
ipc.rs

1use std::sync::mpsc::{Receiver, Sender, channel};
2
3use blake3::Hash;
4use selene_core::library::collectable::Collectable;
5use serde::{Deserialize, Serialize, de::DeserializeOwned};
6use thiserror::Error;
7
8use crate::{
9    LoopMode, ShuffleMode,
10    client::{CallbackFn, IpcHandleError, SeleneClient},
11    player::{PlaybackStatus, PlayerQueryFlags, QueryResult},
12};
13
14#[repr(u8)]
15pub(crate) enum PacketType {
16    /// Unknown packet. Either the client is out of date, or the daemon has a logic bug
17    Unknown,
18
19    /// An event, can be sent without input
20    Event,
21
22    /// A response to a command
23    Response,
24
25    /// A [`PacketError`]
26    Error,
27
28    /// Client has been disconnected from the daemon. This can happen from a manual disconnect, or from the daemon shutting down
29    Disconnect,
30}
31
32#[derive(Debug, Error, Serialize, Deserialize, Clone, Copy)]
33pub enum PacketError {
34    #[error("Packet size '{size}' too large: Max size is {max_size}")]
35    PacketTooLarge { size: usize, max_size: usize },
36
37    #[error("Client was disconnected while waiting for a packet")]
38    Disconnect,
39}
40
41impl From<u8> for PacketType {
42    fn from(value: u8) -> Self {
43        match value {
44            1 => Self::Event,
45            2 => Self::Response,
46            3 => Self::Error,
47            4 => Self::Disconnect,
48            _ => Self::Unknown,
49        }
50    }
51}
52impl SeleneClient {
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: Vec<Collectable>) {
72        self.handle_tx.action(IpcCommand::Play {
73            collectables: collectable,
74        });
75    }
76
77    pub fn ipc_stop(&self) {
78        self.handle_tx.action(IpcCommand::Stop);
79    }
80
81    pub fn ipc_next(&self) {
82        self.handle_tx.action(IpcCommand::Next);
83    }
84
85    pub fn ipc_previous(&self) {
86        self.handle_tx.action(IpcCommand::Previous);
87    }
88
89    pub fn ipc_set_is_playing(&self, is_playing: bool) {
90        self.handle_tx
91            .action(IpcCommand::SetIsPlaying { is_playing });
92    }
93
94    pub fn ipc_toggle_is_playing(&self) -> Result<PlaybackStatus, PacketError> {
95        self.handle_tx.request(IpcCommand::TogglePlaying)
96    }
97
98    pub fn ipc_seek(&self, time: f64, increment: bool) -> Result<Option<f64>, PacketError> {
99        self.handle_tx.request(IpcCommand::Seek {
100            seconds: time,
101            increment,
102        })
103    }
104
105    pub fn ipc_set_volume(&self, volume: f32, increment: bool) -> Result<f32, PacketError> {
106        self.handle_tx
107            .request(IpcCommand::SetVolume { volume, increment })
108    }
109
110    // Queue
111    pub fn ipc_queue_set(
112        &self,
113        tracks: Vec<Collectable>,
114        expected_state: Hash,
115    ) -> Result<bool, PacketError> {
116        self.handle_tx.request(IpcCommand::QueueSet {
117            tracks,
118            expected_state,
119        })
120    }
121
122    pub fn ipc_queue_extend(&self, tracks: Vec<Collectable>) {
123        self.handle_tx.action(IpcCommand::QueueExtend(tracks));
124    }
125
126    pub fn ipc_queue_shuffle(&self) {
127        self.handle_tx.action(IpcCommand::QueueShuffle);
128    }
129
130    pub fn ipc_queue_clear(&self) {
131        self.handle_tx.action(IpcCommand::QueueClear);
132    }
133
134    // Playlist
135    pub fn ipc_playlist_set(
136        &self,
137        collectables: Vec<Collectable>,
138        expected_state: Hash,
139    ) -> Result<bool, PacketError> {
140        self.handle_tx.request(IpcCommand::PlaylistSet {
141            collectables,
142            expected_state,
143        })
144    }
145
146    pub fn ipc_playlist_extend(&self, collectables: Vec<Collectable>) {
147        self.handle_tx
148            .action(IpcCommand::PlaylistExtend(collectables));
149    }
150
151    pub fn ipc_playlist_clear(&self) {
152        self.handle_tx.action(IpcCommand::PlaylistClear);
153    }
154
155    pub fn ipc_playlist_set_shuffle_mode(&self, shuffle_mode: ShuffleMode) {
156        self.handle_tx
157            .action(IpcCommand::PlaylistSetShuffleMode { shuffle_mode });
158    }
159
160    pub fn ipc_playlist_set_loop_mode(&self, loop_mode: LoopMode) {
161        self.handle_tx
162            .action(IpcCommand::PlaylistSetLoopMode { loop_mode });
163    }
164
165    // Tracklist
166    pub fn ipc_tracklist_seek(&self, index: isize, increment: bool) -> Result<usize, PacketError> {
167        self.handle_tx
168            .request(IpcCommand::TracklistSeek { index, increment })
169    }
170
171    // Queries
172    pub fn ipc_query(&self, flags: PlayerQueryFlags) -> Result<QueryResult, PacketError> {
173        self.handle_tx.request(IpcCommand::GetState { flags })
174    }
175}
176
177#[derive(Debug, Serialize, Deserialize, Clone)]
178#[non_exhaustive]
179pub enum IpcCommand {
180    // Generic
181    Flush,
182    Disconnect,
183    ReloadConfig,
184
185    // Playback
186    Play {
187        collectables: Vec<Collectable>,
188    },
189    Stop,
190    SetIsPlaying {
191        is_playing: bool,
192    },
193    TogglePlaying,
194    Seek {
195        seconds: f64,
196        increment: bool,
197    },
198
199    SetVolume {
200        volume: f32,
201        increment: bool,
202    },
203
204    // The override queue
205    QueueSet {
206        tracks: Vec<Collectable>,
207        expected_state: Hash,
208    },
209    QueueExtend(Vec<Collectable>),
210    QueueShuffle,
211    QueueClear,
212
213    // Playlist
214    PlaylistSet {
215        collectables: Vec<Collectable>,
216        expected_state: Hash,
217    },
218    PlaylistExtend(Vec<Collectable>),
219    PlaylistClear,
220
221    // Playlist // Shuffle mode
222    PlaylistSetShuffleMode {
223        shuffle_mode: ShuffleMode,
224    },
225
226    // Playlist // Loop mode
227    PlaylistSetLoopMode {
228        loop_mode: LoopMode,
229    },
230
231    // Tracklist
232    TracklistSeek {
233        index: isize,
234        increment: bool,
235    },
236    Next,
237    Previous,
238
239    // Queries
240    GetState {
241        flags: PlayerQueryFlags,
242    },
243}
244
245impl IpcCommand {
246    #[must_use]
247    pub fn responds(&self) -> bool {
248        matches!(
249            self,
250            IpcCommand::Flush
251                | IpcCommand::TogglePlaying
252                | IpcCommand::Seek { .. }
253                | IpcCommand::SetVolume { .. }
254                | IpcCommand::QueueSet { .. }
255                | IpcCommand::PlaylistSet { .. }
256                | IpcCommand::TracklistSeek { .. }
257                | IpcCommand::GetState { .. }
258        )
259    }
260}
261
262pub enum IpcRequest {
263    Ipc {
264        command: IpcCommand,
265        callback: Option<CallbackFn>,
266    },
267    Reconnect {
268        callback: Sender<Result<(), IpcHandleError>>,
269    },
270}
271
272pub trait IpcTx {
273    fn no_response(&self, command: IpcCommand) -> Result<(), PacketError>;
274
275    fn request<T: DeserializeOwned + Send + 'static>(
276        &self,
277        command: IpcCommand,
278    ) -> Result<T, PacketError>;
279
280    fn action(&self, command: IpcCommand);
281
282    fn disconnect(&self);
283
284    fn reconnect(&self) -> Result<(), IpcHandleError>;
285}
286
287impl IpcTx for Sender<IpcRequest> {
288    fn no_response(&self, command: IpcCommand) -> Result<(), PacketError> {
289        self.send(IpcRequest::Ipc {
290            command,
291            callback: None,
292        })
293        .unwrap();
294
295        Ok(())
296    }
297
298    fn request<T: DeserializeOwned + Send + 'static>(
299        &self,
300        command: IpcCommand,
301    ) -> Result<T, PacketError> {
302        assert!(
303            command.responds(),
304            "Commands must always respond with request()"
305        );
306
307        let (tx, rx) = channel();
308        let callback: CallbackFn = Box::new(move |result| {
309            let _ = tx.send(
310                result.map(|bytes| postcard::from_bytes(bytes).expect("Daemon sent invalid bytes")),
311            );
312        });
313
314        self.send(IpcRequest::Ipc {
315            command,
316            callback: Some(Box::new(callback)),
317        })
318        .unwrap();
319
320        rx.recv().unwrap()
321    }
322
323    fn action(&self, command: IpcCommand) {
324        assert!(
325            !command.responds(),
326            "Commands must never respond with action()"
327        );
328
329        self.send(IpcRequest::Ipc {
330            command,
331            callback: None,
332        })
333        .unwrap();
334    }
335
336    fn disconnect(&self) {
337        let _ = self.send(IpcRequest::Ipc {
338            command: IpcCommand::Disconnect,
339            callback: None,
340        });
341    }
342
343    fn reconnect(&self) -> Result<(), IpcHandleError> {
344        let (tx, rx) = channel();
345        self.send(IpcRequest::Reconnect { callback: tx })
346            .map_err(|_| IpcHandleError::HandleDied)?;
347        rx.recv().map_err(|_| IpcHandleError::HandleDied)?
348    }
349}
350
351pub(crate) trait IpcRx {
352    fn wait_for_reconnect_request(&self) -> Option<Sender<Result<(), IpcHandleError>>>;
353}
354
355impl IpcRx for Receiver<IpcRequest> {
356    fn wait_for_reconnect_request(&self) -> Option<Sender<Result<(), IpcHandleError>>> {
357        loop {
358            match self.recv() {
359                Ok(IpcRequest::Ipc { callback, .. }) => {
360                    if let Some(callback) = callback {
361                        callback(Err(PacketError::Disconnect));
362                    }
363
364                    continue;
365                }
366                Ok(IpcRequest::Reconnect { callback }) => return Some(callback),
367                Err(_) => return None,
368            }
369        }
370    }
371}