mecomp_core/
rpc.rs

1//! This module contains the service definitions.
2
3#![allow(clippy::future_not_send)]
4
5use std::{
6    net::{IpAddr, Ipv4Addr, SocketAddr},
7    ops::Range,
8    path::PathBuf,
9    time::Duration,
10};
11
12use mecomp_storage::db::schemas::{
13    RecordId,
14    album::{Album, AlbumBrief},
15    artist::{Artist, ArtistBrief},
16    collection::{Collection, CollectionBrief},
17    dynamic::{DynamicPlaylist, DynamicPlaylistChangeSet, query::Query},
18    playlist::{Playlist, PlaylistBrief},
19    song::{Song, SongBrief},
20};
21use one_or_many::OneOrMany;
22use serde::{Deserialize, Serialize};
23use tarpc::{client, tokio_serde::formats::Json};
24
25use crate::{
26    errors::{ConnectionError, SerializableLibraryError},
27    state::{
28        RepeatMode, SeekType, StateAudio,
29        library::{LibraryBrief, LibraryFull, LibraryHealth},
30    },
31};
32
33pub type SongId = RecordId;
34pub type ArtistId = RecordId;
35pub type AlbumId = RecordId;
36pub type CollectionId = RecordId;
37pub type PlaylistId = RecordId;
38pub type DynamicPlaylistId = RecordId;
39
40#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
41pub struct SearchResult {
42    pub songs: Box<[SongBrief]>,
43    pub albums: Box<[AlbumBrief]>,
44    pub artists: Box<[ArtistBrief]>,
45}
46
47impl SearchResult {
48    #[must_use]
49    #[inline]
50    pub const fn len(&self) -> usize {
51        self.songs.len() + self.albums.len() + self.artists.len()
52    }
53
54    #[must_use]
55    #[inline]
56    pub const fn is_empty(&self) -> bool {
57        self.songs.is_empty() && self.albums.is_empty() && self.artists.is_empty()
58    }
59}
60
61// TODO: commands for reading songs by paths, artists by name, etc.
62
63/// The music player service, implemented by the music player daemon.
64#[tarpc::service]
65pub trait MusicPlayer {
66    /// Register a UDP listener with the daemon.
67    async fn register_listener(listener_addr: SocketAddr) -> ();
68
69    // misc
70    async fn ping() -> String;
71
72    // Music library.
73    /// Rescans the music library, only error is if a rescan is already in progress.
74    async fn library_rescan() -> Result<(), SerializableLibraryError>;
75    /// Check if a rescan is in progress.
76    async fn library_rescan_in_progress() -> bool;
77    /// Analyze the music library, only error is if an analysis is already in progress.
78    async fn library_analyze(overwrite: bool) -> Result<(), SerializableLibraryError>;
79    /// Check if an analysis is in progress.
80    async fn library_analyze_in_progress() -> bool;
81    /// Recluster the music library, only error is if a recluster is already in progress.
82    async fn library_recluster() -> Result<(), SerializableLibraryError>;
83    /// Check if a recluster is in progress.
84    async fn library_recluster_in_progress() -> bool;
85    /// Returns brief information about the music library.
86    async fn library_brief() -> Result<LibraryBrief, SerializableLibraryError>;
87    /// Returns full information about the music library. (all songs, artists, albums, etc.)
88    async fn library_full() -> Result<LibraryFull, SerializableLibraryError>;
89    /// Returns brief information about the music library's artists.
90    async fn library_artists_brief() -> Result<Box<[ArtistBrief]>, SerializableLibraryError>;
91    /// Returns full information about the music library's artists.
92    async fn library_artists_full() -> Result<Box<[Artist]>, SerializableLibraryError>;
93    /// Returns brief information about the music library's albums.
94    async fn library_albums_brief() -> Result<Box<[AlbumBrief]>, SerializableLibraryError>;
95    /// Returns full information about the music library's albums.
96    async fn library_albums_full() -> Result<Box<[Album]>, SerializableLibraryError>;
97    /// Returns brief information about the music library's songs.
98    async fn library_songs_brief() -> Result<Box<[SongBrief]>, SerializableLibraryError>;
99    /// Returns full information about the music library's songs.
100    async fn library_songs_full() -> Result<Box<[Song]>, SerializableLibraryError>;
101    /// Returns brief information about the users playlists.
102    async fn library_playlists_brief() -> Result<Box<[PlaylistBrief]>, SerializableLibraryError>;
103    /// Returns full information about the users playlists.
104    async fn library_playlists_full() -> Result<Box<[Playlist]>, SerializableLibraryError>;
105    /// Return brief information about the users collections.
106    async fn library_collections_brief() -> Result<Box<[CollectionBrief]>, SerializableLibraryError>;
107    /// Return full information about the users collections.
108    async fn library_collections_full() -> Result<Box<[Collection]>, SerializableLibraryError>;
109    /// Returns information about the health of the music library (are there any missing files, etc.)
110    async fn library_health() -> Result<LibraryHealth, SerializableLibraryError>;
111
112    // music library CRUD operations
113    /// Get a song by its ID.
114    async fn library_song_get(id: SongId) -> Option<Song>;
115    /// Get a song by its path.
116    async fn library_song_get_by_path(path: PathBuf) -> Option<Song>;
117    /// Get the artists of a song.
118    async fn library_song_get_artist(id: SongId) -> OneOrMany<Artist>;
119    /// Get the album of a song.
120    async fn library_song_get_album(id: SongId) -> Option<Album>;
121    /// Get the Playlists a song is in.
122    async fn library_song_get_playlists(id: SongId) -> Box<[Playlist]>;
123    /// Get the Collections a song is in.
124    async fn library_song_get_collections(id: SongId) -> Box<[Collection]>;
125    /// Get an album by its ID.
126    async fn library_album_get(id: AlbumId) -> Option<Album>;
127    /// Get the artists of an album
128    async fn library_album_get_artist(id: AlbumId) -> OneOrMany<Artist>;
129    /// Get the songs of an album
130    async fn library_album_get_songs(id: AlbumId) -> Option<Box<[Song]>>;
131    /// Get an artist by its ID.
132    async fn library_artist_get(id: ArtistId) -> Option<Artist>;
133    /// Get the songs of an artist
134    async fn library_artist_get_songs(id: ArtistId) -> Option<Box<[Song]>>;
135    /// Get the albums of an artist
136    async fn library_artist_get_albums(id: ArtistId) -> Option<Box<[Album]>>;
137
138    // Daemon control.
139    /// tells the daemon to shutdown.
140    async fn daemon_shutdown() -> ();
141
142    // State retrieval.
143    /// returns full information about the current state of the audio player (queue, current song, etc.)
144    async fn state_audio() -> Option<StateAudio>;
145
146    // Current (audio state)
147    /// returns the current artist.
148    async fn current_artist() -> OneOrMany<Artist>;
149    /// returns the current album.
150    async fn current_album() -> Option<Album>;
151    /// returns the current song.
152    async fn current_song() -> Option<SongBrief>;
153
154    // Rand (audio state)
155    /// returns a random artist.
156    async fn rand_artist() -> Option<ArtistBrief>;
157    /// returns a random album.
158    async fn rand_album() -> Option<AlbumBrief>;
159    /// returns a random song.
160    async fn rand_song() -> Option<SongBrief>;
161
162    // Search (fuzzy keys)
163    /// returns a list of artists, albums, and songs matching the given search query.
164    async fn search(query: String, limit: usize) -> SearchResult;
165    /// returns a list of artists matching the given search query.
166    async fn search_artist(query: String, limit: usize) -> Box<[ArtistBrief]>;
167    /// returns a list of albums matching the given search query.
168    async fn search_album(query: String, limit: usize) -> Box<[AlbumBrief]>;
169    /// returns a list of songs matching the given search query.
170    async fn search_song(query: String, limit: usize) -> Box<[SongBrief]>;
171
172    // Playback control.
173    /// toggles playback (play/pause).
174    async fn playback_toggle() -> ();
175    /// start playback (unpause).
176    async fn playback_play() -> ();
177    /// pause playback.
178    async fn playback_pause() -> ();
179    /// stop playback.
180    async fn playback_stop() -> ();
181    /// restart the current song.
182    async fn playback_restart() -> ();
183    /// skip forward by the given amount of songs
184    async fn playback_skip_forward(amount: usize) -> ();
185    /// go backwards by the given amount of songs.
186    async fn playback_skip_backward(amount: usize) -> ();
187    /// only clear the player (i.e. stop playback)
188    async fn playback_clear_player() -> ();
189    /// clears the queue and stops playback.
190    async fn playback_clear() -> ();
191    /// seek forwards, backwards, or to an absolute second in the current song.
192    async fn playback_seek(seek: SeekType, duration: Duration) -> ();
193    /// set the repeat mode.
194    async fn playback_repeat(mode: RepeatMode) -> ();
195    /// Shuffle the current queue, then start playing from the 1st Song in the queue.
196    async fn playback_shuffle() -> ();
197    /// set the volume to the given value
198    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will multiply each sample by this value.
199    async fn playback_volume(volume: f32) -> ();
200    /// increase the volume by the given amount
201    async fn playback_volume_up(amount: f32) -> ();
202    /// decrease the volume by the given amount
203    async fn playback_volume_down(amount: f32) -> ();
204    /// toggle the volume mute.
205    async fn playback_volume_toggle_mute() -> ();
206    /// mute the volume.
207    async fn playback_mute() -> ();
208    /// unmute the volume.
209    async fn playback_unmute() -> ();
210
211    // Queue control.
212    /// add a thing to the queue.
213    /// (if the queue is empty, it will start playing the song.)
214    async fn queue_add(thing: RecordId) -> Result<(), SerializableLibraryError>;
215    /// add a list of things to the queue.
216    /// (if the queue is empty, it will start playing the first thing in the list.)
217    async fn queue_add_list(list: Vec<RecordId>) -> Result<(), SerializableLibraryError>;
218    /// set the current song to a queue index.
219    /// if the index is out of bounds, it will be clamped to the nearest valid index.
220    async fn queue_set_index(index: usize) -> ();
221    /// remove a range of songs from the queue.
222    /// if the range is out of bounds, it will be clamped to the nearest valid range.
223    async fn queue_remove_range(range: Range<usize>) -> ();
224
225    // Playlists.
226    /// create a new playlist with the given name (if it does not already exist).
227    async fn playlist_get_or_create(name: String) -> Result<PlaylistId, SerializableLibraryError>;
228    /// remove a playlist.
229    async fn playlist_remove(id: PlaylistId) -> Result<(), SerializableLibraryError>;
230    /// clone a playlist.
231    /// (creates a new playlist with the same name (append "copy") and contents as the given playlist.)
232    /// returns the id of the new playlist.
233    async fn playlist_clone(id: PlaylistId) -> Result<PlaylistId, SerializableLibraryError>;
234    /// get the id of a playlist.
235    /// returns none if the playlist does not exist.
236    async fn playlist_get_id(name: String) -> Option<PlaylistId>;
237    /// remove a list of songs from a playlist.
238    /// if the songs are not in the playlist, this will do nothing.
239    async fn playlist_remove_songs(
240        playlist: PlaylistId,
241        songs: Vec<SongId>,
242    ) -> Result<(), SerializableLibraryError>;
243    /// Add a thing to a playlist.
244    /// If the thing is something that has songs (an album, artist, etc.), it will add all the songs.
245    async fn playlist_add(
246        playlist: PlaylistId,
247        thing: RecordId,
248    ) -> Result<(), SerializableLibraryError>;
249    /// Add a list of things to a playlist.
250    /// If the things are something that have songs (an album, artist, etc.), it will add all the songs.
251    async fn playlist_add_list(
252        playlist: PlaylistId,
253        list: Vec<RecordId>,
254    ) -> Result<(), SerializableLibraryError>;
255    /// Get a playlist by its ID.
256    async fn playlist_get(id: PlaylistId) -> Option<Playlist>;
257    /// Get the songs of a playlist
258    async fn playlist_get_songs(id: PlaylistId) -> Option<Box<[Song]>>;
259    /// Rename a playlist.
260    async fn playlist_rename(
261        id: PlaylistId,
262        name: String,
263    ) -> Result<Playlist, SerializableLibraryError>;
264    /// Export a playlist to a .m3u file
265    async fn playlist_export(id: PlaylistId, path: PathBuf)
266    -> Result<(), SerializableLibraryError>;
267    /// Import a playlist from a .m3u file
268    async fn playlist_import(
269        path: PathBuf,
270        name: Option<String>,
271    ) -> Result<PlaylistId, SerializableLibraryError>;
272
273    // Auto Curration commands.
274    // (collections, radios, smart playlists, etc.)
275    /// Collections: get a collection by its ID.
276    async fn collection_get(id: CollectionId) -> Option<Collection>;
277    /// Collections: freeze a collection (convert it to a playlist).
278    async fn collection_freeze(
279        id: CollectionId,
280        name: String,
281    ) -> Result<PlaylistId, SerializableLibraryError>;
282    /// Get the songs of a collection
283    async fn collection_get_songs(id: CollectionId) -> Option<Box<[Song]>>;
284
285    // Radio commands.
286    /// Radio: get the `n` most similar songs to the given things.
287    async fn radio_get_similar(
288        things: Vec<RecordId>,
289        n: u32,
290    ) -> Result<Box<[Song]>, SerializableLibraryError>;
291    /// Radio: get the ids of the `n` most similar songs to the given things.
292    async fn radio_get_similar_ids(
293        things: Vec<RecordId>,
294        n: u32,
295    ) -> Result<Box<[SongId]>, SerializableLibraryError>;
296
297    // Dynamic playlist commands
298    /// Dynamic Playlists: create a new DP with the given name and query
299    async fn dynamic_playlist_create(
300        name: String,
301        query: Query,
302    ) -> Result<DynamicPlaylistId, SerializableLibraryError>;
303    /// Dynamic Playlists: list all DPs
304    async fn dynamic_playlist_list() -> Box<[DynamicPlaylist]>;
305    /// Dynamic Playlists: update a DP
306    async fn dynamic_playlist_update(
307        id: DynamicPlaylistId,
308        changes: DynamicPlaylistChangeSet,
309    ) -> Result<DynamicPlaylist, SerializableLibraryError>;
310    /// Dynamic Playlists: remove a DP
311    async fn dynamic_playlist_remove(id: DynamicPlaylistId)
312    -> Result<(), SerializableLibraryError>;
313    /// Dynamic Playlists: get a DP by its ID
314    async fn dynamic_playlist_get(id: DynamicPlaylistId) -> Option<DynamicPlaylist>;
315    /// Dynamic Playlists: get the songs of a DP
316    async fn dynamic_playlist_get_songs(id: DynamicPlaylistId) -> Option<Box<[Song]>>;
317    /// Dynamic Playlists: export dynamic playlists to a csv file
318    async fn dynamic_playlist_export(path: PathBuf) -> Result<(), SerializableLibraryError>;
319    /// Dynamic Playlists: import dynamic playlists from a csv file
320    async fn dynamic_playlist_import(
321        path: PathBuf,
322    ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError>;
323}
324
325/// Initialize the music player client
326///
327/// # Errors
328///
329/// If the client cannot be initialized, an error is returned.
330#[allow(clippy::missing_inline_in_public_items)]
331pub async fn init_client(rpc_port: u16) -> Result<MusicPlayerClient, std::io::Error> {
332    let server_addr = (IpAddr::V4(Ipv4Addr::LOCALHOST), rpc_port);
333
334    let mut transport = tarpc::serde_transport::tcp::connect(server_addr, Json::default);
335    transport.config_mut().max_frame_length(usize::MAX);
336
337    // MusicPlayerClient is generated by the service attribute. It has a constructor `new` that takes a
338    // config and any Transport as input.
339    Ok(MusicPlayerClient::new(client::Config::default(), transport.await?).spawn())
340}
341
342/// Initialize a client to the Music Player Daemon, with `MAX_RETRIES` retries spaced `DELAY` seconds apart
343///
344/// Will log intermediate failures as warnings.
345///
346/// # Errors
347///
348/// Fails if the maximum number of retries was exceeded
349#[allow(clippy::missing_inline_in_public_items)]
350pub async fn init_client_with_retry<const MAX_RETRIES: u64, const DELAY: u64>(
351    rpc_port: u16,
352) -> Result<MusicPlayerClient, ConnectionError> {
353    let mut retries = 0u64;
354
355    while retries < MAX_RETRIES {
356        match init_client(rpc_port).await {
357            Ok(client) => return Ok(client),
358            Err(e) => {
359                retries += 1;
360                log::warn!("Failed to connect to daemon: {e}");
361                tokio::time::sleep(Duration::from_secs(DELAY * retries)).await;
362            }
363        }
364    }
365
366    log::error!("{MAX_RETRIES} retries exceeded when attempting to connect to the daemon");
367
368    Err(ConnectionError::new(rpc_port, MAX_RETRIES))
369}