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::services;
44
45#[derive(Clone, Debug)]
46pub struct MusicPlayerServer {
47    db: Arc<Surreal<Db>>,
48    settings: Arc<Settings>,
49    audio_kernel: Arc<AudioKernelSender>,
50    library_rescan_lock: Arc<Mutex<()>>,
51    library_analyze_lock: Arc<Mutex<()>>,
52    collection_recluster_lock: Arc<Mutex<()>>,
53    publisher: Arc<RwLock<Sender<Message>>>,
54}
55
56impl MusicPlayerServer {
57    #[must_use]
58    #[inline]
59    pub fn new(
60        db: Arc<Surreal<Db>>,
61        settings: Arc<Settings>,
62        audio_kernel: Arc<AudioKernelSender>,
63        event_publisher: Arc<RwLock<Sender<Message>>>,
64    ) -> Self {
65        Self {
66            db,
67            publisher: event_publisher,
68            settings,
69            audio_kernel,
70            library_rescan_lock: Arc::new(Mutex::new(())),
71            library_analyze_lock: Arc::new(Mutex::new(())),
72            collection_recluster_lock: Arc::new(Mutex::new(())),
73        }
74    }
75
76    /// Publish a message to all listeners.
77    ///
78    /// # Errors
79    ///
80    /// Returns an error if the message could not be sent or encoded.
81    #[instrument]
82    pub async fn publish(
83        &self,
84        message: impl Into<Message> + Send + Sync + std::fmt::Debug,
85    ) -> Result<(), mecomp_core::errors::UdpError> {
86        self.publisher.read().await.send(message).await
87    }
88}
89
90#[allow(clippy::missing_inline_in_public_items)]
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    async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
626        info!("Searching for: {query}");
627        // basic idea:
628        // 1. search for songs
629        // 2. search for albums
630        // 3. search for artists
631        // 4. return the results
632        let songs = Song::search(&self.db, &query, i64::from(limit))
633            .await
634            .tap_err(|e| warn!("Error in search: {e}"))
635            .unwrap_or_default()
636            .into();
637
638        let albums = Album::search(&self.db, &query, i64::from(limit))
639            .await
640            .tap_err(|e| warn!("Error in search: {e}"))
641            .unwrap_or_default()
642            .into();
643
644        let artists = Artist::search(&self.db, &query, i64::from(limit))
645            .await
646            .tap_err(|e| warn!("Error in search: {e}"))
647            .unwrap_or_default()
648            .into();
649        SearchResult {
650            songs,
651            albums,
652            artists,
653        }
654    }
655    /// returns a list of artists matching the given search query.
656    #[instrument]
657    async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
658        info!("Searching for artist: {query}");
659        Artist::search(&self.db, &query, i64::from(limit))
660            .await
661            .tap_err(|e| {
662                warn!("Error in search_artist: {e}");
663            })
664            .unwrap_or_default()
665            .into()
666    }
667    /// returns a list of albums matching the given search query.
668    #[instrument]
669    async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
670        info!("Searching for album: {query}");
671        Album::search(&self.db, &query, i64::from(limit))
672            .await
673            .tap_err(|e| {
674                warn!("Error in search_album: {e}");
675            })
676            .unwrap_or_default()
677            .into()
678    }
679    /// returns a list of songs matching the given search query.
680    #[instrument]
681    async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
682        info!("Searching for song: {query}");
683        Song::search(&self.db, &query, i64::from(limit))
684            .await
685            .tap_err(|e| {
686                warn!("Error in search_song: {e}");
687            })
688            .unwrap_or_default()
689            .into()
690    }
691
692    /// toggles playback (play/pause).
693    #[instrument]
694    async fn playback_toggle(self, context: Context) {
695        info!("Toggling playback");
696        self.audio_kernel.send(AudioCommand::TogglePlayback);
697    }
698    /// start playback (unpause).
699    #[instrument]
700    async fn playback_play(self, context: Context) {
701        info!("Starting playback");
702        self.audio_kernel.send(AudioCommand::Play);
703    }
704    /// pause playback.
705    #[instrument]
706    async fn playback_pause(self, context: Context) {
707        info!("Pausing playback");
708        self.audio_kernel.send(AudioCommand::Pause);
709    }
710    /// stop playback.
711    #[instrument]
712    async fn playback_stop(self, context: Context) {
713        info!("Stopping playback");
714        self.audio_kernel.send(AudioCommand::Stop);
715    }
716    /// restart the current song.
717    #[instrument]
718    async fn playback_restart(self, context: Context) {
719        info!("Restarting current song");
720        self.audio_kernel.send(AudioCommand::RestartSong);
721    }
722    /// skip forward by the given amount of songs
723    #[instrument]
724    async fn playback_skip_forward(self, context: Context, amount: usize) {
725        info!("Skipping forward by {amount} songs");
726        self.audio_kernel
727            .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
728    }
729    /// go backwards by the given amount of songs.
730    #[instrument]
731    async fn playback_skip_backward(self, context: Context, amount: usize) {
732        info!("Going back by {amount} songs");
733        self.audio_kernel
734            .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
735    }
736    /// stop playback.
737    /// (clears the queue and stops playback)
738    #[instrument]
739    async fn playback_clear_player(self, context: Context) {
740        info!("Stopping playback");
741        self.audio_kernel.send(AudioCommand::ClearPlayer);
742    }
743    /// clear the queue.
744    #[instrument]
745    async fn playback_clear(self, context: Context) {
746        info!("Clearing queue and stopping playback");
747        self.audio_kernel
748            .send(AudioCommand::Queue(QueueCommand::Clear));
749    }
750    /// seek forwards, backwards, or to an absolute second in the current song.
751    #[instrument]
752    async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
753        info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
754        self.audio_kernel.send(AudioCommand::Seek(seek, duration));
755    }
756    /// set the repeat mode.
757    #[instrument]
758    async fn playback_repeat(self, context: Context, mode: RepeatMode) {
759        info!("Setting repeat mode to: {}", mode);
760        self.audio_kernel
761            .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
762    }
763    /// Shuffle the current queue, then start playing from the 1st Song in the queue.
764    #[instrument]
765    async fn playback_shuffle(self, context: Context) {
766        info!("Shuffling queue");
767        self.audio_kernel
768            .send(AudioCommand::Queue(QueueCommand::Shuffle));
769    }
770    /// set the volume to the given value
771    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will multiply each sample by this value.
772    #[instrument]
773    async fn playback_volume(self, context: Context, volume: f32) {
774        info!("Setting volume to: {volume}",);
775        self.audio_kernel
776            .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
777    }
778    /// increase the volume by the given amount
779    #[instrument]
780    async fn playback_volume_up(self, context: Context, amount: f32) {
781        info!("Increasing volume by: {amount}",);
782        self.audio_kernel
783            .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
784    }
785    /// decrease the volume by the given amount
786    #[instrument]
787    async fn playback_volume_down(self, context: Context, amount: f32) {
788        info!("Decreasing volume by: {amount}",);
789        self.audio_kernel
790            .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
791    }
792    /// toggle the volume mute.
793    #[instrument]
794    async fn playback_volume_toggle_mute(self, context: Context) {
795        info!("Toggling volume mute");
796        self.audio_kernel
797            .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
798    }
799    /// mute the volume.
800    #[instrument]
801    async fn playback_mute(self, context: Context) {
802        info!("Muting volume");
803        self.audio_kernel
804            .send(AudioCommand::Volume(VolumeCommand::Mute));
805    }
806    /// unmute the volume.
807    #[instrument]
808    async fn playback_unmute(self, context: Context) {
809        info!("Unmuting volume");
810        self.audio_kernel
811            .send(AudioCommand::Volume(VolumeCommand::Unmute));
812    }
813
814    /// add a song to the queue.
815    /// (if the queue is empty, it will start playing the song.)
816    #[instrument]
817    async fn queue_add(
818        self,
819        context: Context,
820        thing: schemas::Thing,
821    ) -> Result<(), SerializableLibraryError> {
822        info!("Adding thing to queue: {thing}");
823
824        let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
825
826        if songs.is_empty() {
827            return Err(Error::NotFound.into());
828        }
829
830        self.audio_kernel
831            .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
832                songs,
833            ))));
834
835        Ok(())
836    }
837    /// add a list of things to the queue.
838    /// (if the queue is empty, it will start playing the first thing in the list.)
839    #[instrument]
840    async fn queue_add_list(
841        self,
842        context: Context,
843        list: Vec<schemas::Thing>,
844    ) -> Result<(), SerializableLibraryError> {
845        info!(
846            "Adding list to queue: ({})",
847            list.iter()
848                .map(ToString::to_string)
849                .collect::<Vec<_>>()
850                .join(", ")
851        );
852
853        // go through the list, and get songs for each thing (depending on what it is)
854        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
855
856        self.audio_kernel
857            .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
858                songs,
859            ))));
860
861        Ok(())
862    }
863    /// set the current song to a queue index.
864    /// if the index is out of bounds, it will be clamped to the nearest valid index.
865    #[instrument]
866    async fn queue_set_index(self, context: Context, index: usize) {
867        info!("Setting queue index to: {index}");
868
869        self.audio_kernel
870            .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
871    }
872    /// remove a range of songs from the queue.
873    /// if the range is out of bounds, it will be clamped to the nearest valid range.
874    #[instrument]
875    async fn queue_remove_range(self, context: Context, range: Range<usize>) {
876        info!("Removing queue range: {range:?}");
877
878        self.audio_kernel
879            .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
880    }
881
882    /// Returns brief information about the users playlists.
883    #[instrument]
884    async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
885        info!("Listing playlists");
886        Playlist::read_all(&self.db)
887            .await
888            .tap_err(|e| warn!("Error in playlist_list: {e}"))
889            .ok()
890            .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
891            .unwrap_or_default()
892    }
893    /// create a new playlist.
894    /// if a playlist with the same name already exists, this will return that playlist's id in the error variant
895    #[instrument]
896    async fn playlist_get_or_create(
897        self,
898        context: Context,
899        name: String,
900    ) -> Result<PlaylistId, SerializableLibraryError> {
901        info!("Creating new playlist: {name}");
902
903        // see if a playlist with that name already exists
904        match Playlist::read_by_name(&self.db, name.clone()).await {
905            Ok(Some(playlist)) => return Ok(playlist.id.into()),
906            Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
907            _ => {}
908        }
909        // if it doesn't, create a new playlist with that name
910        match Playlist::create(
911            &self.db,
912            Playlist {
913                id: Playlist::generate_id(),
914                name,
915                runtime: Duration::from_secs(0),
916                song_count: 0,
917            },
918        )
919        .await
920        .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
921        {
922            Some(playlist) => Ok(playlist.id.into()),
923            None => Err(Error::NotCreated.into()),
924        }
925    }
926    /// remove a playlist.
927    #[instrument]
928    async fn playlist_remove(
929        self,
930        context: Context,
931        id: PlaylistId,
932    ) -> Result<(), SerializableLibraryError> {
933        let id = id.into();
934        info!("Removing playlist with id: {id}");
935
936        Playlist::delete(&self.db, id)
937            .await?
938            .ok_or(Error::NotFound)?;
939
940        Ok(())
941    }
942    /// clone a playlist.
943    /// (creates a new playlist with the same name (append " (copy)") and contents as the given playlist.)
944    /// returns the id of the new playlist
945    #[instrument]
946    async fn playlist_clone(
947        self,
948        context: Context,
949        id: PlaylistId,
950    ) -> Result<PlaylistId, SerializableLibraryError> {
951        let id = id.into();
952        info!("Cloning playlist with id: {id}");
953
954        let new_playlist = Playlist::create_copy(&self.db, id)
955            .await?
956            .ok_or(Error::NotFound)?;
957
958        Ok(new_playlist.id.into())
959    }
960    /// get the id of a playlist.
961    /// returns none if the playlist does not exist.
962    #[instrument]
963    async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
964        info!("Getting playlist ID: {name}");
965
966        Playlist::read_by_name(&self.db, name)
967            .await
968            .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
969            .ok()
970            .flatten()
971            .map(|playlist| playlist.id.into())
972    }
973    /// remove a list of songs from a playlist.
974    /// if the songs are not in the playlist, this will do nothing.
975    #[instrument]
976    async fn playlist_remove_songs(
977        self,
978        context: Context,
979        playlist: PlaylistId,
980        songs: Vec<SongId>,
981    ) -> Result<(), SerializableLibraryError> {
982        let playlist = playlist.into();
983        let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
984        info!("Removing song from playlist: {playlist} ({songs:?})");
985
986        Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
987    }
988    /// Add a thing to a playlist.
989    /// If the thing is something that has songs (an album, artist, etc.), it will add all the songs.
990    #[instrument]
991    async fn playlist_add(
992        self,
993        context: Context,
994        playlist: PlaylistId,
995        thing: schemas::Thing,
996    ) -> Result<(), SerializableLibraryError> {
997        let playlist = playlist.into();
998        info!("Adding thing to playlist: {playlist} ({thing})");
999
1000        // get songs for the thing
1001        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1002
1003        Ok(Playlist::add_songs(
1004            &self.db,
1005            playlist,
1006            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1007        )
1008        .await?)
1009    }
1010    /// Add a list of things to a playlist.
1011    /// If the things are something that have songs (an album, artist, etc.), it will add all the songs.
1012    #[instrument]
1013    async fn playlist_add_list(
1014        self,
1015        context: Context,
1016        playlist: PlaylistId,
1017        list: Vec<schemas::Thing>,
1018    ) -> Result<(), SerializableLibraryError> {
1019        let playlist = playlist.into();
1020        info!(
1021            "Adding list to playlist: {playlist} ({})",
1022            list.iter()
1023                .map(ToString::to_string)
1024                .collect::<Vec<_>>()
1025                .join(", ")
1026        );
1027
1028        // go through the list, and get songs for each thing (depending on what it is)
1029        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1030
1031        Ok(Playlist::add_songs(
1032            &self.db,
1033            playlist,
1034            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1035        )
1036        .await?)
1037    }
1038    /// Get a playlist by its ID.
1039    #[instrument]
1040    async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1041        let id = id.into();
1042        info!("Getting playlist by ID: {}", id);
1043
1044        Playlist::read(&self.db, id)
1045            .await
1046            .tap_err(|e| warn!("Error in playlist_get: {e}"))
1047            .ok()
1048            .flatten()
1049    }
1050    /// Get the songs of a playlist
1051    #[instrument]
1052    async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1053        let id = id.into();
1054        info!("Getting songs in: {id}");
1055        Playlist::read_songs(&self.db, id)
1056            .await
1057            .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1058            .ok()
1059            .map(Into::into)
1060    }
1061    /// Rename a playlist.
1062    #[instrument]
1063    async fn playlist_rename(
1064        self,
1065        context: Context,
1066        id: PlaylistId,
1067        name: String,
1068    ) -> Result<Playlist, SerializableLibraryError> {
1069        let id = id.into();
1070        info!("Renaming playlist: {id} ({name})");
1071        Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1072            .await?
1073            .ok_or(Error::NotFound.into())
1074    }
1075
1076    /// Collections: Return brief information about the users auto curration collections.
1077    #[instrument]
1078    async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1079        info!("Listing collections");
1080        Collection::read_all(&self.db)
1081            .await
1082            .tap_err(|e| warn!("Error in collection_list: {e}"))
1083            .ok()
1084            .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1085            .unwrap_or_default()
1086    }
1087    /// Collections: get a collection by its ID.
1088    #[instrument]
1089    async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1090        info!("Getting collection by ID: {id:?}");
1091        Collection::read(&self.db, id.into())
1092            .await
1093            .tap_err(|e| warn!("Error in collection_get: {e}"))
1094            .ok()
1095            .flatten()
1096    }
1097    /// Collections: freeze a collection (convert it to a playlist).
1098    #[instrument]
1099    async fn collection_freeze(
1100        self,
1101        context: Context,
1102        id: CollectionId,
1103        name: String,
1104    ) -> Result<PlaylistId, SerializableLibraryError> {
1105        info!("Freezing collection: {id:?} ({name})");
1106        Ok(Collection::freeze(&self.db, id.into(), name)
1107            .await
1108            .map(|p| p.id.into())?)
1109    }
1110    /// Get the songs of a collection
1111    #[instrument]
1112    async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1113        let id = id.into();
1114        info!("Getting songs in: {id}");
1115        Collection::read_songs(&self.db, id)
1116            .await
1117            .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1118            .ok()
1119            .map(Into::into)
1120    }
1121
1122    /// Radio: get the `n` most similar songs to the given things.
1123    #[instrument]
1124    async fn radio_get_similar(
1125        self,
1126        context: Context,
1127        things: Vec<schemas::Thing>,
1128        n: u32,
1129    ) -> Result<Box<[Song]>, SerializableLibraryError> {
1130        #[cfg(not(feature = "analysis"))]
1131        {
1132            warn!("Analysis is not enabled");
1133            return Err(SerializableLibraryError::AnalysisNotEnabled);
1134        }
1135
1136        #[cfg(feature = "analysis")]
1137        {
1138            info!("Getting the {n} most similar songs to: {things:?}");
1139            Ok(services::radio::get_similar(&self.db, things, n)
1140                .await
1141                .map(Vec::into_boxed_slice)
1142                .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1143        }
1144    }
1145    /// Radio: get the ids of the `n` most similar songs to the given things.
1146    #[instrument]
1147    async fn radio_get_similar_ids(
1148        self,
1149        context: Context,
1150        things: Vec<schemas::Thing>,
1151        n: u32,
1152    ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1153        #[cfg(not(feature = "analysis"))]
1154        {
1155            warn!("Analysis is not enabled");
1156            return Err(SerializableLibraryError::AnalysisNotEnabled);
1157        }
1158
1159        #[cfg(feature = "analysis")]
1160        {
1161            info!("Getting the {n} most similar songs to: {things:?}");
1162            Ok(services::radio::get_similar(&self.db, things, n)
1163                .await
1164                .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1165                .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1166        }
1167    }
1168
1169    // Dynamic playlist commands
1170    /// Dynamic Playlists: create a new DP with the given name and query
1171    #[instrument]
1172    async fn dynamic_playlist_create(
1173        self,
1174        context: Context,
1175        name: String,
1176        query: Query,
1177    ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1178        let id = DynamicPlaylist::generate_id();
1179        info!("Creating new DP: {id:?} ({name})");
1180
1181        match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1182            .await
1183            .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1184        {
1185            Some(dp) => Ok(dp.id.into()),
1186            None => Err(Error::NotCreated.into()),
1187        }
1188    }
1189    /// Dynamic Playlists: list all DPs
1190    #[instrument]
1191    async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1192        info!("Listing DPs");
1193        DynamicPlaylist::read_all(&self.db)
1194            .await
1195            .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1196            .ok()
1197            .map(Into::into)
1198            .unwrap_or_default()
1199    }
1200    /// Dynamic Playlists: update a DP
1201    #[instrument]
1202    async fn dynamic_playlist_update(
1203        self,
1204        context: Context,
1205        id: DynamicPlaylistId,
1206        changes: DynamicPlaylistChangeSet,
1207    ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1208        info!("Updating DP: {id:?}, {changes:?}");
1209        DynamicPlaylist::update(&self.db, id.into(), changes)
1210            .await
1211            .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1212            .ok_or(Error::NotFound.into())
1213    }
1214    /// Dynamic Playlists: remove a DP
1215    #[instrument]
1216    async fn dynamic_playlist_remove(
1217        self,
1218        context: Context,
1219        id: DynamicPlaylistId,
1220    ) -> Result<(), SerializableLibraryError> {
1221        info!("Removing DP with id: {id:?}");
1222        DynamicPlaylist::delete(&self.db, id.into())
1223            .await?
1224            .ok_or(Error::NotFound)?;
1225        Ok(())
1226    }
1227    /// Dynamic Playlists: get a DP by its ID
1228    #[instrument]
1229    async fn dynamic_playlist_get(
1230        self,
1231        context: Context,
1232        id: DynamicPlaylistId,
1233    ) -> Option<DynamicPlaylist> {
1234        info!("Getting DP by ID: {id:?}");
1235        DynamicPlaylist::read(&self.db, id.into())
1236            .await
1237            .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1238            .ok()
1239            .flatten()
1240    }
1241    /// Dynamic Playlists: get the songs of a DP
1242    #[instrument]
1243    async fn dynamic_playlist_get_songs(
1244        self,
1245        context: Context,
1246        id: DynamicPlaylistId,
1247    ) -> Option<Box<[Song]>> {
1248        info!("Getting songs in DP: {id:?}");
1249        DynamicPlaylist::run_query_by_id(&self.db, id.into())
1250            .await
1251            .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1252            .ok()
1253            .flatten()
1254            .map(Into::into)
1255    }
1256}