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