mecomp_daemon/
controller.rs

1//----------------------------------------------------------------------------------------- std lib
2use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
3//--------------------------------------------------------------------------------- other libraries
4use ::tarpc::context::Context;
5use log::{debug, error, info, warn};
6use rand::seq::SliceRandom;
7use surrealdb::{engine::local::Db, Surreal};
8use tap::TapFallible;
9use tokio::sync::{Mutex, RwLock};
10use tracing::{instrument, Instrument};
11//-------------------------------------------------------------------------------- MECOMP libraries
12use mecomp_core::{
13    audio::{
14        commands::{AudioCommand, QueueCommand, VolumeCommand},
15        AudioKernelSender,
16    },
17    config::Settings,
18    errors::SerializableLibraryError,
19    rpc::{
20        AlbumId, ArtistId, CollectionId, DynamicPlaylistId, MusicPlayer, PlaylistId, SearchResult,
21        SongId,
22    },
23    state::{
24        library::{LibraryBrief, LibraryFull, LibraryHealth},
25        RepeatMode, SeekType, StateAudio,
26    },
27    udp::{Event, Message, Sender},
28};
29use mecomp_storage::{
30    db::schemas::{
31        self,
32        album::{Album, AlbumBrief},
33        artist::{Artist, ArtistBrief},
34        collection::{Collection, CollectionBrief},
35        dynamic::{query::Query, DynamicPlaylist, DynamicPlaylistChangeSet},
36        playlist::{Playlist, PlaylistBrief, PlaylistChangeSet},
37        song::{Song, SongBrief},
38    },
39    errors::Error,
40};
41use one_or_many::OneOrMany;
42
43use crate::{
44    services,
45    termination::{self, Terminator},
46};
47
48#[derive(Clone, Debug)]
49pub struct MusicPlayerServer {
50    db: Arc<Surreal<Db>>,
51    settings: Arc<Settings>,
52    audio_kernel: Arc<AudioKernelSender>,
53    library_rescan_lock: Arc<Mutex<()>>,
54    library_analyze_lock: Arc<Mutex<()>>,
55    collection_recluster_lock: Arc<Mutex<()>>,
56    publisher: Arc<RwLock<Sender<Message>>>,
57    terminator: Arc<Mutex<Terminator>>,
58}
59
60impl MusicPlayerServer {
61    #[must_use]
62    #[inline]
63    pub fn new(
64        db: Arc<Surreal<Db>>,
65        settings: Arc<Settings>,
66        audio_kernel: Arc<AudioKernelSender>,
67        event_publisher: Arc<RwLock<Sender<Message>>>,
68        terminator: Terminator,
69    ) -> Self {
70        Self {
71            db,
72            publisher: event_publisher,
73            settings,
74            audio_kernel,
75            library_rescan_lock: Arc::new(Mutex::new(())),
76            library_analyze_lock: Arc::new(Mutex::new(())),
77            collection_recluster_lock: Arc::new(Mutex::new(())),
78            terminator: Arc::new(Mutex::new(terminator)),
79        }
80    }
81
82    /// Publish a message to all listeners.
83    ///
84    /// # Errors
85    ///
86    /// Returns an error if the message could not be sent or encoded.
87    #[instrument]
88    pub async fn publish(
89        &self,
90        message: impl Into<Message> + Send + Sync + std::fmt::Debug,
91    ) -> Result<(), mecomp_core::errors::UdpError> {
92        self.publisher.read().await.send(message).await
93    }
94}
95
96#[allow(clippy::missing_inline_in_public_items)]
97impl MusicPlayer for MusicPlayerServer {
98    #[instrument]
99    async fn register_listener(self, context: Context, listener_addr: std::net::SocketAddr) {
100        info!("Registering listener: {listener_addr}");
101        self.publisher.write().await.add_subscriber(listener_addr);
102    }
103
104    async fn ping(self, _: Context) -> String {
105        "pong".to_string()
106    }
107
108    /// Rescans the music library, only error is if a rescan is already in progress.
109    #[instrument]
110    async fn library_rescan(self, context: Context) -> Result<(), SerializableLibraryError> {
111        info!("Rescanning library");
112
113        if self.library_rescan_lock.try_lock().is_err() {
114            warn!("Library rescan already in progress");
115            return Err(SerializableLibraryError::RescanInProgress);
116        }
117
118        let span = tracing::Span::current();
119
120        std::thread::Builder::new()
121            .name(String::from("Library Rescan"))
122            .spawn(move || {
123                futures::executor::block_on(
124                    async {
125                        let _guard = self.library_rescan_lock.lock().await;
126                        match services::library::rescan(
127                            &self.db,
128                            &self.settings.daemon.library_paths,
129                            &self.settings.daemon.artist_separator,
130                            self.settings.daemon.genre_separator.as_deref(),
131                            self.settings.daemon.conflict_resolution,
132                        )
133                        .await
134                        {
135                            Ok(()) => info!("Library rescan complete"),
136                            Err(e) => error!("Error in library_rescan: {e}"),
137                        }
138
139                        let result = self.publish(Event::LibraryRescanFinished).await;
140                        if let Err(e) = result {
141                            error!("Error notifying clients that library_rescan_finished: {e}");
142                        }
143                    }
144                    .instrument(span),
145                );
146            })?;
147
148        Ok(())
149    }
150    /// Check if a rescan is in progress.
151    #[instrument]
152    async fn library_rescan_in_progress(self, context: Context) -> bool {
153        self.library_rescan_lock.try_lock().is_err()
154    }
155    /// Analyze the music library, only error is if an analysis is already in progress.
156    #[instrument]
157    async fn library_analyze(
158        self,
159        context: Context,
160        overwrite: bool,
161    ) -> Result<(), SerializableLibraryError> {
162        #[cfg(not(feature = "analysis"))]
163        {
164            warn!("Analysis is not enabled");
165            return Err(SerializableLibraryError::AnalysisNotEnabled);
166        }
167
168        #[cfg(feature = "analysis")]
169        {
170            info!("Analyzing library");
171
172            if self.library_analyze_lock.try_lock().is_err() {
173                warn!("Library analysis already in progress");
174                return Err(SerializableLibraryError::AnalysisInProgress);
175            }
176            let span = tracing::Span::current();
177
178            std::thread::Builder::new()
179                .name(String::from("Library Analysis"))
180                .spawn(move || {
181                    futures::executor::block_on(
182                        async {
183                            let _guard = self.library_analyze_lock.lock().await;
184                            match services::library::analyze(&self.db, overwrite).await {
185                                Ok(()) => info!("Library analysis complete"),
186                                Err(e) => error!("Error in library_analyze: {e}"),
187                            }
188
189                            let result = &self.publish(Event::LibraryAnalysisFinished).await;
190                            if let Err(e) = result {
191                                error!(
192                                    "Error notifying clients that library_analysis_finished: {e}"
193                                );
194                            }
195                        }
196                        .instrument(span),
197                    );
198                })?;
199
200            Ok(())
201        }
202    }
203    /// Check if an analysis is in progress.
204    #[instrument]
205    async fn library_analyze_in_progress(self, context: Context) -> bool {
206        self.library_analyze_lock.try_lock().is_err()
207    }
208    /// Recluster the music library, only error is if a recluster is already in progress.
209    #[instrument]
210    async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
211        #[cfg(not(feature = "analysis"))]
212        {
213            warn!("Analysis is not enabled");
214            return Err(SerializableLibraryError::AnalysisNotEnabled);
215        }
216
217        #[cfg(feature = "analysis")]
218        {
219            info!("Reclustering collections");
220
221            if self.collection_recluster_lock.try_lock().is_err() {
222                warn!("Collection reclustering already in progress");
223                return Err(SerializableLibraryError::ReclusterInProgress);
224            }
225
226            let span = tracing::Span::current();
227
228            std::thread::Builder::new()
229                .name(String::from("Collection Recluster"))
230                .spawn(move || {
231                    futures::executor::block_on(
232                        async {
233                            let _guard = self.collection_recluster_lock.lock().await;
234                            match services::library::recluster(
235                                &self.db,
236                                &self.settings.reclustering,
237                            )
238                            .await
239                            {
240                                Ok(()) => info!("Collection reclustering complete"),
241                                Err(e) => error!("Error in collection_recluster: {e}"),
242                            }
243
244                            let result = &self.publish(Event::LibraryReclusterFinished).await;
245                            if let Err(e) = result {
246                                error!(
247                                    "Error notifying clients that library_recluster_finished: {e}"
248                                );
249                            }
250                        }
251                        .instrument(span),
252                    );
253                })?;
254
255            Ok(())
256        }
257    }
258    /// Check if a recluster is in progress.
259    #[instrument]
260    async fn library_recluster_in_progress(self, context: Context) -> bool {
261        self.collection_recluster_lock.try_lock().is_err()
262    }
263    /// Returns brief information about the music library.
264    #[instrument]
265    async fn library_brief(
266        self,
267        context: Context,
268    ) -> Result<LibraryBrief, SerializableLibraryError> {
269        info!("Creating library brief");
270        Ok(services::library::brief(&self.db)
271            .await
272            .tap_err(|e| warn!("Error in library_brief: {e}"))?)
273    }
274    /// Returns full information about the music library. (all songs, artists, albums, etc.)
275    #[instrument]
276    async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
277        info!("Creating library full");
278        Ok(services::library::full(&self.db)
279            .await
280            .tap_err(|e| warn!("Error in library_full: {e}"))?)
281    }
282    /// Returns brief information about the music library's artists.
283    #[instrument]
284    async fn library_artists_brief(
285        self,
286        context: Context,
287    ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
288        info!("Creating library artists brief");
289        Ok(Artist::read_all(&self.db)
290            .await
291            .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
292            .iter()
293            .map(std::convert::Into::into)
294            .collect())
295    }
296    /// Returns full information about the music library's artists.
297    #[instrument]
298    async fn library_artists_full(
299        self,
300        context: Context,
301    ) -> Result<Box<[Artist]>, SerializableLibraryError> {
302        info!("Creating library artists full");
303        Ok(Artist::read_all(&self.db)
304            .await
305            .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
306            .into_boxed_slice())
307    }
308    /// Returns brief information about the music library's albums.
309    #[instrument]
310    async fn library_albums_brief(
311        self,
312        context: Context,
313    ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
314        info!("Creating library albums brief");
315        Ok(Album::read_all(&self.db)
316            .await
317            .tap_err(|e| warn!("Error in library_albums_brief: {e}"))?
318            .iter()
319            .map(std::convert::Into::into)
320            .collect())
321    }
322    /// Returns full information about the music library's albums.
323    #[instrument]
324    async fn library_albums_full(
325        self,
326        context: Context,
327    ) -> Result<Box<[Album]>, SerializableLibraryError> {
328        info!("Creating library albums full");
329        Ok(Album::read_all(&self.db)
330            .await
331            .map(std::vec::Vec::into_boxed_slice)
332            .tap_err(|e| warn!("Error in library_albums_full: {e}"))?)
333    }
334    /// Returns brief information about the music library's songs.
335    #[instrument]
336    async fn library_songs_brief(
337        self,
338        context: Context,
339    ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
340        info!("Creating library songs brief");
341        Ok(Song::read_all(&self.db)
342            .await
343            .tap_err(|e| warn!("Error in library_songs_brief: {e}"))?
344            .iter()
345            .map(std::convert::Into::into)
346            .collect())
347    }
348    /// Returns full information about the music library's songs.
349    #[instrument]
350    async fn library_songs_full(
351        self,
352        context: Context,
353    ) -> Result<Box<[Song]>, SerializableLibraryError> {
354        info!("Creating library songs full");
355        Ok(Song::read_all(&self.db)
356            .await
357            .map(std::vec::Vec::into_boxed_slice)
358            .tap_err(|e| warn!("Error in library_songs_full: {e}"))?)
359    }
360    /// Returns information about the health of the music library (are there any missing files, etc.)
361    #[instrument]
362    async fn library_health(
363        self,
364        context: Context,
365    ) -> Result<LibraryHealth, SerializableLibraryError> {
366        info!("Creating library health");
367        Ok(services::library::health(&self.db)
368            .await
369            .tap_err(|e| warn!("Error in library_health: {e}"))?)
370    }
371    /// Get a song by its ID.
372    #[instrument]
373    async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
374        let id = id.into();
375        info!("Getting song by ID: {id}");
376        Song::read(&self.db, id)
377            .await
378            .tap_err(|e| warn!("Error in library_song_get: {e}"))
379            .ok()
380            .flatten()
381    }
382    /// Get a song by its file path.
383    #[instrument]
384    async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
385        info!("Getting song by path: {}", path.display());
386        Song::read_by_path(&self.db, path)
387            .await
388            .tap_err(|e| warn!("Error in library_song_get_by_path: {e}"))
389            .ok()
390            .flatten()
391    }
392    /// Get the artists of a song.
393    #[instrument]
394    async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
395        let id = id.into();
396        info!("Getting artist of: {id}");
397        Song::read_artist(&self.db, id)
398            .await
399            .tap_err(|e| warn!("Error in library_song_get_artist: {e}"))
400            .ok()
401            .into()
402    }
403    /// Get the album of a song.
404    #[instrument]
405    async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
406        let id = id.into();
407        info!("Getting album of: {id}");
408        Song::read_album(&self.db, id)
409            .await
410            .tap_err(|e| warn!("Error in library_song_get_album: {e}"))
411            .ok()
412            .flatten()
413    }
414    /// Get the Playlists a song is in.
415    #[instrument]
416    async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
417        let id = id.into();
418        info!("Getting playlists of: {id}");
419        Song::read_playlists(&self.db, id)
420            .await
421            .tap_err(|e| warn!("Error in library_song_get_playlists: {e}"))
422            .ok()
423            .unwrap_or_default()
424            .into()
425    }
426    /// Get the Collections a song is in.
427    #[instrument]
428    async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
429        let id = id.into();
430        info!("Getting collections of: {id}");
431        Song::read_collections(&self.db, id)
432            .await
433            .tap_err(|e| warn!("Error in library_song_get_collections: {e}"))
434            .ok()
435            .unwrap_or_default()
436            .into()
437    }
438
439    /// Get an album by its ID.
440    #[instrument]
441    async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
442        let id = id.into();
443        info!("Getting album by ID: {id}");
444        Album::read(&self.db, id)
445            .await
446            .tap_err(|e| warn!("Error in library_album_get: {e}"))
447            .ok()
448            .flatten()
449    }
450    /// Get the artists of an album
451    #[instrument]
452    async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
453        let id = id.into();
454        info!("Getting artists of: {id}");
455        Album::read_artist(&self.db, id)
456            .await
457            .tap_err(|e| warn!("Error in library_album_get_artist: {e}"))
458            .ok()
459            .into()
460    }
461    /// Get the songs of an album
462    #[instrument]
463    async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
464        let id = id.into();
465        info!("Getting songs of: {id}");
466        Album::read_songs(&self.db, id)
467            .await
468            .tap_err(|e| warn!("Error in library_album_get_songs: {e}"))
469            .ok()
470            .map(Into::into)
471    }
472    /// Get an artist by its ID.
473    #[instrument]
474    async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
475        let id = id.into();
476        info!("Getting artist by ID: {id}");
477        Artist::read(&self.db, id)
478            .await
479            .tap_err(|e| warn!("Error in library_artist_get: {e}"))
480            .ok()
481            .flatten()
482    }
483    /// Get the songs of an artist
484    #[instrument]
485    async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
486        let id = id.into();
487        info!("Getting songs of: {id}");
488        Artist::read_songs(&self.db, id)
489            .await
490            .tap_err(|e| warn!("Error in library_artist_get_songs: {e}"))
491            .ok()
492            .map(Into::into)
493    }
494    /// Get the albums of an artist
495    #[instrument]
496    async fn library_artist_get_albums(
497        self,
498        context: Context,
499        id: ArtistId,
500    ) -> Option<Box<[Album]>> {
501        let id = id.into();
502        info!("Getting albums of: {id}");
503        Artist::read_albums(&self.db, id)
504            .await
505            .tap_err(|e| warn!("Error in library_artist_get_albums: {e}"))
506            .ok()
507            .map(Into::into)
508    }
509
510    /// tells the daemon to shutdown.
511    #[instrument]
512    async fn daemon_shutdown(self, context: Context) {
513        let terminator = self.terminator.clone();
514        std::thread::Builder::new()
515            .name(String::from("Daemon Shutdown"))
516            .spawn(move || {
517                std::thread::sleep(std::time::Duration::from_secs(1));
518                let terminate_result = terminator
519                    .blocking_lock()
520                    .terminate(termination::Interrupted::UserInt);
521                if let Err(e) = terminate_result {
522                    error!("Error terminating daemon, panicking instead: {e}");
523                    panic!("Error terminating daemon: {e}");
524                }
525            })
526            .unwrap();
527        info!("Shutting down daemon in 1 second");
528    }
529
530    /// returns full information about the current state of the audio player (queue, current song, etc.)
531    #[instrument]
532    async fn state_audio(self, context: Context) -> Option<StateAudio> {
533        debug!("Getting state of audio player");
534        let (tx, rx) = tokio::sync::oneshot::channel();
535
536        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
537
538        rx.await
539            .tap_err(|e| warn!("Error in state_audio: {e}"))
540            .ok()
541    }
542
543    /// returns the current artist.
544    #[instrument]
545    async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
546        info!("Getting current artist");
547        let (tx, rx) = tokio::sync::oneshot::channel();
548
549        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
550
551        if let Some(song) = rx
552            .await
553            .tap_err(|e| warn!("Error in current_artist: {e}"))
554            .ok()
555            .and_then(|state| state.current_song)
556        {
557            Song::read_artist(&self.db, song.id)
558                .await
559                .tap_err(|e| warn!("Error in current_album: {e}"))
560                .ok()
561                .into()
562        } else {
563            OneOrMany::None
564        }
565    }
566    /// returns the current album.
567    #[instrument]
568    async fn current_album(self, context: Context) -> Option<Album> {
569        info!("Getting current album");
570        let (tx, rx) = tokio::sync::oneshot::channel();
571
572        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
573
574        if let Some(song) = rx
575            .await
576            .tap_err(|e| warn!("Error in current_album: {e}"))
577            .ok()
578            .and_then(|state| state.current_song)
579        {
580            Song::read_album(&self.db, song.id)
581                .await
582                .tap_err(|e| warn!("Error in current_album: {e}"))
583                .ok()
584                .flatten()
585        } else {
586            None
587        }
588    }
589    /// returns the current song.
590    #[instrument]
591    async fn current_song(self, context: Context) -> Option<Song> {
592        info!("Getting current song");
593        let (tx, rx) = tokio::sync::oneshot::channel();
594
595        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
596
597        rx.await
598            .tap_err(|e| warn!("Error in current_song: {e}"))
599            .ok()
600            .and_then(|state| state.current_song)
601    }
602
603    /// returns a random artist.
604    #[instrument]
605    async fn rand_artist(self, context: Context) -> Option<Artist> {
606        info!("Getting random artist");
607        Artist::read_all(&self.db)
608            .await
609            .tap_err(|e| warn!("Error in rand_artist: {e}"))
610            .ok()
611            .and_then(|artists| artists.choose(&mut rand::thread_rng()).cloned())
612    }
613    /// returns a random album.
614    #[instrument]
615    async fn rand_album(self, context: Context) -> Option<Album> {
616        info!("Getting random album");
617        Album::read_all(&self.db)
618            .await
619            .tap_err(|e| warn!("Error in rand_album: {e}"))
620            .ok()
621            .and_then(|albums| albums.choose(&mut rand::thread_rng()).cloned())
622    }
623    /// returns a random song.
624    #[instrument]
625    async fn rand_song(self, context: Context) -> Option<Song> {
626        info!("Getting random song");
627        Song::read_all(&self.db)
628            .await
629            .tap_err(|e| warn!("Error in rand_song: {e}"))
630            .ok()
631            .and_then(|songs| songs.choose(&mut rand::thread_rng()).cloned())
632    }
633
634    /// returns a list of artists, albums, and songs matching the given search query.
635    #[instrument]
636    async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
637        info!("Searching for: {query}");
638        // basic idea:
639        // 1. search for songs
640        // 2. search for albums
641        // 3. search for artists
642        // 4. return the results
643        let songs = Song::search(&self.db, &query, i64::from(limit))
644            .await
645            .tap_err(|e| warn!("Error in search: {e}"))
646            .unwrap_or_default()
647            .into();
648
649        let albums = Album::search(&self.db, &query, i64::from(limit))
650            .await
651            .tap_err(|e| warn!("Error in search: {e}"))
652            .unwrap_or_default()
653            .into();
654
655        let artists = Artist::search(&self.db, &query, i64::from(limit))
656            .await
657            .tap_err(|e| warn!("Error in search: {e}"))
658            .unwrap_or_default()
659            .into();
660        SearchResult {
661            songs,
662            albums,
663            artists,
664        }
665    }
666    /// returns a list of artists matching the given search query.
667    #[instrument]
668    async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
669        info!("Searching for artist: {query}");
670        Artist::search(&self.db, &query, i64::from(limit))
671            .await
672            .tap_err(|e| {
673                warn!("Error in search_artist: {e}");
674            })
675            .unwrap_or_default()
676            .into()
677    }
678    /// returns a list of albums matching the given search query.
679    #[instrument]
680    async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
681        info!("Searching for album: {query}");
682        Album::search(&self.db, &query, i64::from(limit))
683            .await
684            .tap_err(|e| {
685                warn!("Error in search_album: {e}");
686            })
687            .unwrap_or_default()
688            .into()
689    }
690    /// returns a list of songs matching the given search query.
691    #[instrument]
692    async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
693        info!("Searching for song: {query}");
694        Song::search(&self.db, &query, i64::from(limit))
695            .await
696            .tap_err(|e| {
697                warn!("Error in search_song: {e}");
698            })
699            .unwrap_or_default()
700            .into()
701    }
702
703    /// toggles playback (play/pause).
704    #[instrument]
705    async fn playback_toggle(self, context: Context) {
706        info!("Toggling playback");
707        self.audio_kernel.send(AudioCommand::TogglePlayback);
708    }
709    /// start playback (unpause).
710    #[instrument]
711    async fn playback_play(self, context: Context) {
712        info!("Starting playback");
713        self.audio_kernel.send(AudioCommand::Play);
714    }
715    /// pause playback.
716    #[instrument]
717    async fn playback_pause(self, context: Context) {
718        info!("Pausing playback");
719        self.audio_kernel.send(AudioCommand::Pause);
720    }
721    /// stop playback.
722    #[instrument]
723    async fn playback_stop(self, context: Context) {
724        info!("Stopping playback");
725        self.audio_kernel.send(AudioCommand::Stop);
726    }
727    /// restart the current song.
728    #[instrument]
729    async fn playback_restart(self, context: Context) {
730        info!("Restarting current song");
731        self.audio_kernel.send(AudioCommand::RestartSong);
732    }
733    /// skip forward by the given amount of songs
734    #[instrument]
735    async fn playback_skip_forward(self, context: Context, amount: usize) {
736        info!("Skipping forward by {amount} songs");
737        self.audio_kernel
738            .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
739    }
740    /// go backwards by the given amount of songs.
741    #[instrument]
742    async fn playback_skip_backward(self, context: Context, amount: usize) {
743        info!("Going back by {amount} songs");
744        self.audio_kernel
745            .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
746    }
747    /// stop playback.
748    /// (clears the queue and stops playback)
749    #[instrument]
750    async fn playback_clear_player(self, context: Context) {
751        info!("Stopping playback");
752        self.audio_kernel.send(AudioCommand::ClearPlayer);
753    }
754    /// clear the queue.
755    #[instrument]
756    async fn playback_clear(self, context: Context) {
757        info!("Clearing queue and stopping playback");
758        self.audio_kernel
759            .send(AudioCommand::Queue(QueueCommand::Clear));
760    }
761    /// seek forwards, backwards, or to an absolute second in the current song.
762    #[instrument]
763    async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
764        info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
765        self.audio_kernel.send(AudioCommand::Seek(seek, duration));
766    }
767    /// set the repeat mode.
768    #[instrument]
769    async fn playback_repeat(self, context: Context, mode: RepeatMode) {
770        info!("Setting repeat mode to: {mode}");
771        self.audio_kernel
772            .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
773    }
774    /// Shuffle the current queue, then start playing from the 1st Song in the queue.
775    #[instrument]
776    async fn playback_shuffle(self, context: Context) {
777        info!("Shuffling queue");
778        self.audio_kernel
779            .send(AudioCommand::Queue(QueueCommand::Shuffle));
780    }
781    /// set the volume to the given value
782    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will multiply each sample by this value.
783    #[instrument]
784    async fn playback_volume(self, context: Context, volume: f32) {
785        info!("Setting volume to: {volume}",);
786        self.audio_kernel
787            .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
788    }
789    /// increase the volume by the given amount
790    #[instrument]
791    async fn playback_volume_up(self, context: Context, amount: f32) {
792        info!("Increasing volume by: {amount}",);
793        self.audio_kernel
794            .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
795    }
796    /// decrease the volume by the given amount
797    #[instrument]
798    async fn playback_volume_down(self, context: Context, amount: f32) {
799        info!("Decreasing volume by: {amount}",);
800        self.audio_kernel
801            .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
802    }
803    /// toggle the volume mute.
804    #[instrument]
805    async fn playback_volume_toggle_mute(self, context: Context) {
806        info!("Toggling volume mute");
807        self.audio_kernel
808            .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
809    }
810    /// mute the volume.
811    #[instrument]
812    async fn playback_mute(self, context: Context) {
813        info!("Muting volume");
814        self.audio_kernel
815            .send(AudioCommand::Volume(VolumeCommand::Mute));
816    }
817    /// unmute the volume.
818    #[instrument]
819    async fn playback_unmute(self, context: Context) {
820        info!("Unmuting volume");
821        self.audio_kernel
822            .send(AudioCommand::Volume(VolumeCommand::Unmute));
823    }
824
825    /// add a song to the queue.
826    /// (if the queue is empty, it will start playing the song.)
827    #[instrument]
828    async fn queue_add(
829        self,
830        context: Context,
831        thing: schemas::RecordId,
832    ) -> Result<(), SerializableLibraryError> {
833        info!("Adding thing to queue: {thing}");
834
835        let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
836
837        if songs.is_empty() {
838            return Err(Error::NotFound.into());
839        }
840
841        self.audio_kernel
842            .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
843                songs,
844            ))));
845
846        Ok(())
847    }
848    /// add a list of things to the queue.
849    /// (if the queue is empty, it will start playing the first thing in the list.)
850    #[instrument]
851    async fn queue_add_list(
852        self,
853        context: Context,
854        list: Vec<schemas::RecordId>,
855    ) -> Result<(), SerializableLibraryError> {
856        info!(
857            "Adding list to queue: ({})",
858            list.iter()
859                .map(ToString::to_string)
860                .collect::<Vec<_>>()
861                .join(", ")
862        );
863
864        // go through the list, and get songs for each thing (depending on what it is)
865        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
866
867        self.audio_kernel
868            .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
869                songs,
870            ))));
871
872        Ok(())
873    }
874    /// set the current song to a queue index.
875    /// if the index is out of bounds, it will be clamped to the nearest valid index.
876    #[instrument]
877    async fn queue_set_index(self, context: Context, index: usize) {
878        info!("Setting queue index to: {index}");
879
880        self.audio_kernel
881            .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
882    }
883    /// remove a range of songs from the queue.
884    /// if the range is out of bounds, it will be clamped to the nearest valid range.
885    #[instrument]
886    async fn queue_remove_range(self, context: Context, range: Range<usize>) {
887        info!("Removing queue range: {range:?}");
888
889        self.audio_kernel
890            .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
891    }
892
893    /// Returns brief information about the users playlists.
894    #[instrument]
895    async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
896        info!("Listing playlists");
897        Playlist::read_all(&self.db)
898            .await
899            .tap_err(|e| warn!("Error in playlist_list: {e}"))
900            .ok()
901            .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
902            .unwrap_or_default()
903    }
904    /// create a new playlist.
905    /// if a playlist with the same name already exists, this will return that playlist's id in the error variant
906    #[instrument]
907    async fn playlist_get_or_create(
908        self,
909        context: Context,
910        name: String,
911    ) -> Result<PlaylistId, SerializableLibraryError> {
912        info!("Creating new playlist: {name}");
913
914        // see if a playlist with that name already exists
915        match Playlist::read_by_name(&self.db, name.clone()).await {
916            Ok(Some(playlist)) => return Ok(playlist.id.into()),
917            Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
918            _ => {}
919        }
920        // if it doesn't, create a new playlist with that name
921        match Playlist::create(
922            &self.db,
923            Playlist {
924                id: Playlist::generate_id(),
925                name,
926                runtime: Duration::from_secs(0),
927                song_count: 0,
928            },
929        )
930        .await
931        .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
932        {
933            Some(playlist) => Ok(playlist.id.into()),
934            None => Err(Error::NotCreated.into()),
935        }
936    }
937    /// remove a playlist.
938    #[instrument]
939    async fn playlist_remove(
940        self,
941        context: Context,
942        id: PlaylistId,
943    ) -> Result<(), SerializableLibraryError> {
944        let id = id.into();
945        info!("Removing playlist with id: {id}");
946
947        Playlist::delete(&self.db, id)
948            .await?
949            .ok_or(Error::NotFound)?;
950
951        Ok(())
952    }
953    /// clone a playlist.
954    /// (creates a new playlist with the same name (append " (copy)") and contents as the given playlist.)
955    /// returns the id of the new playlist
956    #[instrument]
957    async fn playlist_clone(
958        self,
959        context: Context,
960        id: PlaylistId,
961    ) -> Result<PlaylistId, SerializableLibraryError> {
962        let id = id.into();
963        info!("Cloning playlist with id: {id}");
964
965        let new_playlist = Playlist::create_copy(&self.db, id)
966            .await?
967            .ok_or(Error::NotFound)?;
968
969        Ok(new_playlist.id.into())
970    }
971    /// get the id of a playlist.
972    /// returns none if the playlist does not exist.
973    #[instrument]
974    async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
975        info!("Getting playlist ID: {name}");
976
977        Playlist::read_by_name(&self.db, name)
978            .await
979            .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
980            .ok()
981            .flatten()
982            .map(|playlist| playlist.id.into())
983    }
984    /// remove a list of songs from a playlist.
985    /// if the songs are not in the playlist, this will do nothing.
986    #[instrument]
987    async fn playlist_remove_songs(
988        self,
989        context: Context,
990        playlist: PlaylistId,
991        songs: Vec<SongId>,
992    ) -> Result<(), SerializableLibraryError> {
993        let playlist = playlist.into();
994        let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
995        info!("Removing song from playlist: {playlist} ({songs:?})");
996
997        Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
998    }
999    /// Add a thing to a playlist.
1000    /// If the thing is something that has songs (an album, artist, etc.), it will add all the songs.
1001    #[instrument]
1002    async fn playlist_add(
1003        self,
1004        context: Context,
1005        playlist: PlaylistId,
1006        thing: schemas::RecordId,
1007    ) -> Result<(), SerializableLibraryError> {
1008        let playlist = playlist.into();
1009        info!("Adding thing to playlist: {playlist} ({thing})");
1010
1011        // get songs for the thing
1012        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1013
1014        Ok(Playlist::add_songs(
1015            &self.db,
1016            playlist,
1017            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1018        )
1019        .await?)
1020    }
1021    /// Add a list of things to a playlist.
1022    /// If the things are something that have songs (an album, artist, etc.), it will add all the songs.
1023    #[instrument]
1024    async fn playlist_add_list(
1025        self,
1026        context: Context,
1027        playlist: PlaylistId,
1028        list: Vec<schemas::RecordId>,
1029    ) -> Result<(), SerializableLibraryError> {
1030        let playlist = playlist.into();
1031        info!(
1032            "Adding list to playlist: {playlist} ({})",
1033            list.iter()
1034                .map(ToString::to_string)
1035                .collect::<Vec<_>>()
1036                .join(", ")
1037        );
1038
1039        // go through the list, and get songs for each thing (depending on what it is)
1040        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1041
1042        Ok(Playlist::add_songs(
1043            &self.db,
1044            playlist,
1045            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1046        )
1047        .await?)
1048    }
1049    /// Get a playlist by its ID.
1050    #[instrument]
1051    async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1052        let id = id.into();
1053        info!("Getting playlist by ID: {id}");
1054
1055        Playlist::read(&self.db, id)
1056            .await
1057            .tap_err(|e| warn!("Error in playlist_get: {e}"))
1058            .ok()
1059            .flatten()
1060    }
1061    /// Get the songs of a playlist
1062    #[instrument]
1063    async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1064        let id = id.into();
1065        info!("Getting songs in: {id}");
1066        Playlist::read_songs(&self.db, id)
1067            .await
1068            .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1069            .ok()
1070            .map(Into::into)
1071    }
1072    /// Rename a playlist.
1073    #[instrument]
1074    async fn playlist_rename(
1075        self,
1076        context: Context,
1077        id: PlaylistId,
1078        name: String,
1079    ) -> Result<Playlist, SerializableLibraryError> {
1080        let id = id.into();
1081        info!("Renaming playlist: {id} ({name})");
1082        Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1083            .await?
1084            .ok_or(Error::NotFound.into())
1085    }
1086
1087    /// Collections: Return brief information about the users auto curration collections.
1088    #[instrument]
1089    async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1090        info!("Listing collections");
1091        Collection::read_all(&self.db)
1092            .await
1093            .tap_err(|e| warn!("Error in collection_list: {e}"))
1094            .ok()
1095            .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1096            .unwrap_or_default()
1097    }
1098    /// Collections: get a collection by its ID.
1099    #[instrument]
1100    async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1101        info!("Getting collection by ID: {id:?}");
1102        Collection::read(&self.db, id.into())
1103            .await
1104            .tap_err(|e| warn!("Error in collection_get: {e}"))
1105            .ok()
1106            .flatten()
1107    }
1108    /// Collections: freeze a collection (convert it to a playlist).
1109    #[instrument]
1110    async fn collection_freeze(
1111        self,
1112        context: Context,
1113        id: CollectionId,
1114        name: String,
1115    ) -> Result<PlaylistId, SerializableLibraryError> {
1116        info!("Freezing collection: {id:?} ({name})");
1117        Ok(Collection::freeze(&self.db, id.into(), name)
1118            .await
1119            .map(|p| p.id.into())?)
1120    }
1121    /// Get the songs of a collection
1122    #[instrument]
1123    async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1124        let id = id.into();
1125        info!("Getting songs in: {id}");
1126        Collection::read_songs(&self.db, id)
1127            .await
1128            .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1129            .ok()
1130            .map(Into::into)
1131    }
1132
1133    /// Radio: get the `n` most similar songs to the given things.
1134    #[instrument]
1135    async fn radio_get_similar(
1136        self,
1137        context: Context,
1138        things: Vec<schemas::RecordId>,
1139        n: u32,
1140    ) -> Result<Box<[Song]>, SerializableLibraryError> {
1141        #[cfg(not(feature = "analysis"))]
1142        {
1143            warn!("Analysis is not enabled");
1144            return Err(SerializableLibraryError::AnalysisNotEnabled);
1145        }
1146
1147        #[cfg(feature = "analysis")]
1148        {
1149            info!("Getting the {n} most similar songs to: {things:?}");
1150            Ok(services::radio::get_similar(&self.db, things, n)
1151                .await
1152                .map(Vec::into_boxed_slice)
1153                .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1154        }
1155    }
1156    /// Radio: get the ids of the `n` most similar songs to the given things.
1157    #[instrument]
1158    async fn radio_get_similar_ids(
1159        self,
1160        context: Context,
1161        things: Vec<schemas::RecordId>,
1162        n: u32,
1163    ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1164        #[cfg(not(feature = "analysis"))]
1165        {
1166            warn!("Analysis is not enabled");
1167            return Err(SerializableLibraryError::AnalysisNotEnabled);
1168        }
1169
1170        #[cfg(feature = "analysis")]
1171        {
1172            info!("Getting the {n} most similar songs to: {things:?}");
1173            Ok(services::radio::get_similar(&self.db, things, n)
1174                .await
1175                .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1176                .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1177        }
1178    }
1179
1180    // Dynamic playlist commands
1181    /// Dynamic Playlists: create a new DP with the given name and query
1182    #[instrument]
1183    async fn dynamic_playlist_create(
1184        self,
1185        context: Context,
1186        name: String,
1187        query: Query,
1188    ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1189        let id = DynamicPlaylist::generate_id();
1190        info!("Creating new DP: {id:?} ({name})");
1191
1192        match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1193            .await
1194            .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1195        {
1196            Some(dp) => Ok(dp.id.into()),
1197            None => Err(Error::NotCreated.into()),
1198        }
1199    }
1200    /// Dynamic Playlists: list all DPs
1201    #[instrument]
1202    async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1203        info!("Listing DPs");
1204        DynamicPlaylist::read_all(&self.db)
1205            .await
1206            .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1207            .ok()
1208            .map(Into::into)
1209            .unwrap_or_default()
1210    }
1211    /// Dynamic Playlists: update a DP
1212    #[instrument]
1213    async fn dynamic_playlist_update(
1214        self,
1215        context: Context,
1216        id: DynamicPlaylistId,
1217        changes: DynamicPlaylistChangeSet,
1218    ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1219        info!("Updating DP: {id:?}, {changes:?}");
1220        DynamicPlaylist::update(&self.db, id.into(), changes)
1221            .await
1222            .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1223            .ok_or(Error::NotFound.into())
1224    }
1225    /// Dynamic Playlists: remove a DP
1226    #[instrument]
1227    async fn dynamic_playlist_remove(
1228        self,
1229        context: Context,
1230        id: DynamicPlaylistId,
1231    ) -> Result<(), SerializableLibraryError> {
1232        info!("Removing DP with id: {id:?}");
1233        DynamicPlaylist::delete(&self.db, id.into())
1234            .await?
1235            .ok_or(Error::NotFound)?;
1236        Ok(())
1237    }
1238    /// Dynamic Playlists: get a DP by its ID
1239    #[instrument]
1240    async fn dynamic_playlist_get(
1241        self,
1242        context: Context,
1243        id: DynamicPlaylistId,
1244    ) -> Option<DynamicPlaylist> {
1245        info!("Getting DP by ID: {id:?}");
1246        DynamicPlaylist::read(&self.db, id.into())
1247            .await
1248            .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1249            .ok()
1250            .flatten()
1251    }
1252    /// Dynamic Playlists: get the songs of a DP
1253    #[instrument]
1254    async fn dynamic_playlist_get_songs(
1255        self,
1256        context: Context,
1257        id: DynamicPlaylistId,
1258    ) -> Option<Box<[Song]>> {
1259        info!("Getting songs in DP: {id:?}");
1260        DynamicPlaylist::run_query_by_id(&self.db, id.into())
1261            .await
1262            .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1263            .ok()
1264            .flatten()
1265            .map(Into::into)
1266    }
1267}