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