1#![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#[tarpc::service]
65pub trait MusicPlayer {
66 async fn register_listener(listener_addr: SocketAddr) -> ();
68
69 async fn ping() -> String;
71
72 async fn library_rescan() -> Result<(), SerializableLibraryError>;
75 async fn library_rescan_in_progress() -> bool;
77 async fn library_analyze(overwrite: bool) -> Result<(), SerializableLibraryError>;
79 async fn library_analyze_in_progress() -> bool;
81 async fn library_recluster() -> Result<(), SerializableLibraryError>;
83 async fn library_recluster_in_progress() -> bool;
85 async fn library_brief() -> Result<LibraryBrief, SerializableLibraryError>;
87 async fn library_full() -> Result<LibraryFull, SerializableLibraryError>;
89 async fn library_artists_brief() -> Result<Box<[ArtistBrief]>, SerializableLibraryError>;
91 async fn library_artists_full() -> Result<Box<[Artist]>, SerializableLibraryError>;
93 async fn library_albums_brief() -> Result<Box<[AlbumBrief]>, SerializableLibraryError>;
95 async fn library_albums_full() -> Result<Box<[Album]>, SerializableLibraryError>;
97 async fn library_songs_brief() -> Result<Box<[SongBrief]>, SerializableLibraryError>;
99 async fn library_songs_full() -> Result<Box<[Song]>, SerializableLibraryError>;
101 async fn library_playlists_brief() -> Result<Box<[PlaylistBrief]>, SerializableLibraryError>;
103 async fn library_playlists_full() -> Result<Box<[Playlist]>, SerializableLibraryError>;
105 async fn library_collections_brief() -> Result<Box<[CollectionBrief]>, SerializableLibraryError>;
107 async fn library_collections_full() -> Result<Box<[Collection]>, SerializableLibraryError>;
109 async fn library_health() -> Result<LibraryHealth, SerializableLibraryError>;
111
112 async fn library_song_get(id: SongId) -> Option<Song>;
115 async fn library_song_get_by_path(path: PathBuf) -> Option<Song>;
117 async fn library_song_get_artist(id: SongId) -> OneOrMany<Artist>;
119 async fn library_song_get_album(id: SongId) -> Option<Album>;
121 async fn library_song_get_playlists(id: SongId) -> Box<[Playlist]>;
123 async fn library_song_get_collections(id: SongId) -> Box<[Collection]>;
125 async fn library_album_get(id: AlbumId) -> Option<Album>;
127 async fn library_album_get_artist(id: AlbumId) -> OneOrMany<Artist>;
129 async fn library_album_get_songs(id: AlbumId) -> Option<Box<[Song]>>;
131 async fn library_artist_get(id: ArtistId) -> Option<Artist>;
133 async fn library_artist_get_songs(id: ArtistId) -> Option<Box<[Song]>>;
135 async fn library_artist_get_albums(id: ArtistId) -> Option<Box<[Album]>>;
137
138 async fn daemon_shutdown() -> ();
141
142 async fn state_audio() -> Option<StateAudio>;
145
146 async fn current_artist() -> OneOrMany<Artist>;
149 async fn current_album() -> Option<Album>;
151 async fn current_song() -> Option<SongBrief>;
153
154 async fn rand_artist() -> Option<ArtistBrief>;
157 async fn rand_album() -> Option<AlbumBrief>;
159 async fn rand_song() -> Option<SongBrief>;
161
162 async fn search(query: String, limit: usize) -> SearchResult;
165 async fn search_artist(query: String, limit: usize) -> Box<[ArtistBrief]>;
167 async fn search_album(query: String, limit: usize) -> Box<[AlbumBrief]>;
169 async fn search_song(query: String, limit: usize) -> Box<[SongBrief]>;
171
172 async fn playback_toggle() -> ();
175 async fn playback_play() -> ();
177 async fn playback_pause() -> ();
179 async fn playback_stop() -> ();
181 async fn playback_restart() -> ();
183 async fn playback_skip_forward(amount: usize) -> ();
185 async fn playback_skip_backward(amount: usize) -> ();
187 async fn playback_clear_player() -> ();
189 async fn playback_clear() -> ();
191 async fn playback_seek(seek: SeekType, duration: Duration) -> ();
193 async fn playback_repeat(mode: RepeatMode) -> ();
195 async fn playback_shuffle() -> ();
197 async fn playback_volume(volume: f32) -> ();
200 async fn playback_volume_up(amount: f32) -> ();
202 async fn playback_volume_down(amount: f32) -> ();
204 async fn playback_volume_toggle_mute() -> ();
206 async fn playback_mute() -> ();
208 async fn playback_unmute() -> ();
210
211 async fn queue_add(thing: RecordId) -> Result<(), SerializableLibraryError>;
215 async fn queue_add_list(list: Vec<RecordId>) -> Result<(), SerializableLibraryError>;
218 async fn queue_set_index(index: usize) -> ();
221 async fn queue_remove_range(range: Range<usize>) -> ();
224
225 async fn playlist_get_or_create(name: String) -> Result<PlaylistId, SerializableLibraryError>;
228 async fn playlist_remove(id: PlaylistId) -> Result<(), SerializableLibraryError>;
230 async fn playlist_clone(id: PlaylistId) -> Result<PlaylistId, SerializableLibraryError>;
234 async fn playlist_get_id(name: String) -> Option<PlaylistId>;
237 async fn playlist_remove_songs(
240 playlist: PlaylistId,
241 songs: Vec<SongId>,
242 ) -> Result<(), SerializableLibraryError>;
243 async fn playlist_add(
246 playlist: PlaylistId,
247 thing: RecordId,
248 ) -> Result<(), SerializableLibraryError>;
249 async fn playlist_add_list(
252 playlist: PlaylistId,
253 list: Vec<RecordId>,
254 ) -> Result<(), SerializableLibraryError>;
255 async fn playlist_get(id: PlaylistId) -> Option<Playlist>;
257 async fn playlist_get_songs(id: PlaylistId) -> Option<Box<[Song]>>;
259 async fn playlist_rename(
261 id: PlaylistId,
262 name: String,
263 ) -> Result<Playlist, SerializableLibraryError>;
264 async fn playlist_export(id: PlaylistId, path: PathBuf)
266 -> Result<(), SerializableLibraryError>;
267 async fn playlist_import(
269 path: PathBuf,
270 name: Option<String>,
271 ) -> Result<PlaylistId, SerializableLibraryError>;
272
273 async fn collection_get(id: CollectionId) -> Option<Collection>;
277 async fn collection_freeze(
279 id: CollectionId,
280 name: String,
281 ) -> Result<PlaylistId, SerializableLibraryError>;
282 async fn collection_get_songs(id: CollectionId) -> Option<Box<[Song]>>;
284
285 async fn radio_get_similar(
288 things: Vec<RecordId>,
289 n: u32,
290 ) -> Result<Box<[Song]>, SerializableLibraryError>;
291 async fn radio_get_similar_ids(
293 things: Vec<RecordId>,
294 n: u32,
295 ) -> Result<Box<[SongId]>, SerializableLibraryError>;
296
297 async fn dynamic_playlist_create(
300 name: String,
301 query: Query,
302 ) -> Result<DynamicPlaylistId, SerializableLibraryError>;
303 async fn dynamic_playlist_list() -> Box<[DynamicPlaylist]>;
305 async fn dynamic_playlist_update(
307 id: DynamicPlaylistId,
308 changes: DynamicPlaylistChangeSet,
309 ) -> Result<DynamicPlaylist, SerializableLibraryError>;
310 async fn dynamic_playlist_remove(id: DynamicPlaylistId)
312 -> Result<(), SerializableLibraryError>;
313 async fn dynamic_playlist_get(id: DynamicPlaylistId) -> Option<DynamicPlaylist>;
315 async fn dynamic_playlist_get_songs(id: DynamicPlaylistId) -> Option<Box<[Song]>>;
317 async fn dynamic_playlist_export(path: PathBuf) -> Result<(), SerializableLibraryError>;
319 async fn dynamic_playlist_import(
321 path: PathBuf,
322 ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError>;
323}
324
325#[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 Ok(MusicPlayerClient::new(client::Config::default(), transport.await?).spawn())
340}
341
342#[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}