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