mecomp_daemon/
controller.rs

1//----------------------------------------------------------------------------------------- std lib
2use std::{fs::File, path::PathBuf, sync::Arc, time::Duration};
3//--------------------------------------------------------------------------------- other libraries
4use log::{debug, error, info, warn};
5use surrealdb::{Surreal, engine::local::Db};
6use tokio::sync::Mutex;
7use tonic::{Code, Request, Response};
8use tracing::{Instrument, instrument};
9//-------------------------------------------------------------------------------- MECOMP libraries
10use mecomp_core::{
11    audio::{
12        AudioKernelSender,
13        commands::{AudioCommand, QueueCommand, VolumeCommand},
14    },
15    config::Settings,
16    errors::BackupError,
17    state::{RepeatMode, SeekType},
18    udp::{Event, Message, Sender},
19};
20use mecomp_prost::{
21    AlbumBriefList, AlbumBriefOption, AlbumOption, ArtistBriefList, ArtistBriefListOption,
22    ArtistBriefOption, ArtistOption, CollectionFreezeRequest, CollectionList, CollectionOption,
23    DynamicPlaylistCreateRequest, DynamicPlaylistList, DynamicPlaylistOption,
24    DynamicPlaylistUpdateRequest, InProgressResponse, LibraryAnalyzeRequest, LibraryBrief,
25    LibraryFull, LibraryHealth, Path, PingResponse, PlaybackRepeatRequest, PlaybackSeekRequest,
26    PlaybackSkipRequest, PlaybackVolumeAdjustRequest, PlaybackVolumeSetRequest,
27    PlaylistAddListRequest, PlaylistAddRequest, PlaylistBrief, PlaylistExportRequest,
28    PlaylistImportRequest, PlaylistList, PlaylistName, PlaylistOption, PlaylistRemoveSongsRequest,
29    PlaylistRenameRequest, QueueRemoveRangeRequest, QueueSetIndexRequest, RadioSimilarRequest,
30    RecordId, RecordIdList, RegisterListenerRequest, SearchRequest, SearchResult, SongBriefList,
31    SongBriefOption, SongOption, StateAudioResponse, Ulid, server::MusicPlayer as MusicPlayerTrait,
32};
33use mecomp_storage::db::schemas::{
34    self,
35    album::Album,
36    artist::Artist,
37    collection::Collection,
38    dynamic::{DynamicPlaylist, DynamicPlaylistChangeSet, query::Query},
39    playlist::{Playlist, PlaylistChangeSet},
40    song::Song,
41};
42use one_or_many::OneOrMany;
43
44use crate::{
45    services::{
46        self,
47        backup::{
48            export_dynamic_playlists, export_playlist, import_dynamic_playlists, import_playlist,
49            validate_file_path,
50        },
51    },
52    termination::{self, Terminator},
53};
54
55#[derive(Clone, Debug)]
56pub struct MusicPlayer {
57    db: Arc<Surreal<Db>>,
58    settings: Arc<Settings>,
59    audio_kernel: Arc<AudioKernelSender>,
60    library_rescan_lock: Arc<Mutex<()>>,
61    library_analyze_lock: Arc<Mutex<()>>,
62    collection_recluster_lock: Arc<Mutex<()>>,
63    publisher: Arc<Sender<Message>>,
64    terminator: Arc<Mutex<Terminator>>,
65    interrupt: Arc<termination::InterruptReceiver>,
66}
67
68impl MusicPlayer {
69    #[must_use]
70    #[inline]
71    pub fn new(
72        db: Arc<Surreal<Db>>,
73        settings: Arc<Settings>,
74        audio_kernel: Arc<AudioKernelSender>,
75        event_publisher: Arc<Sender<Message>>,
76        terminator: Terminator,
77        interrupt: termination::InterruptReceiver,
78    ) -> Self {
79        Self {
80            db,
81            publisher: event_publisher,
82            settings,
83            audio_kernel,
84            library_rescan_lock: Arc::new(Mutex::new(())),
85            library_analyze_lock: Arc::new(Mutex::new(())),
86            collection_recluster_lock: Arc::new(Mutex::new(())),
87            terminator: Arc::new(Mutex::new(terminator)),
88            interrupt: Arc::new(interrupt),
89        }
90    }
91
92    /// Publish a message to all listeners.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error if the message could not be sent or encoded.
97    #[instrument]
98    #[inline]
99    pub async fn publish(
100        &self,
101        message: impl Into<Message> + Send + Sync + std::fmt::Debug,
102    ) -> Result<(), mecomp_core::errors::UdpError> {
103        self.publisher.send(message).await
104    }
105}
106
107type TonicResult<T> = std::result::Result<Response<T>, tonic::Status>;
108
109#[allow(clippy::missing_inline_in_public_items)]
110#[tonic::async_trait]
111impl MusicPlayerTrait for MusicPlayer {
112    #[instrument]
113    async fn register_listener(
114        self: Arc<Self>,
115        request: Request<RegisterListenerRequest>,
116    ) -> TonicResult<()> {
117        let RegisterListenerRequest { host, port } = request.into_inner();
118        let listener_addr = format!("{host}:{port}").parse().map_err(|e| {
119            tonic::Status::invalid_argument(format!("Invalid listener address: {e}"))
120        })?;
121        info!("Registering listener: {listener_addr}");
122        self.publisher.add_subscriber(listener_addr).await;
123        Ok(Response::new(()))
124    }
125    async fn ping(self: Arc<Self>, _: Request<()>) -> TonicResult<PingResponse> {
126        Ok(Response::new(PingResponse {
127            message: "pong".to_string(),
128        }))
129    }
130    async fn daemon_shutdown(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
131        let terminator = self.terminator.clone();
132        std::thread::Builder::new()
133            .name(String::from("Daemon Shutdown"))
134            .spawn(move || {
135                std::thread::sleep(std::time::Duration::from_secs(1));
136                let terminate_result = terminator
137                    .blocking_lock()
138                    .terminate(termination::Interrupted::UserInt);
139                if let Err(e) = terminate_result {
140                    error!("Error terminating daemon, panicking instead: {e}");
141                    panic!("Error terminating daemon: {e}");
142                }
143            })
144            .unwrap();
145        info!("Shutting down daemon in 1 second");
146        Ok(Response::new(()))
147    }
148
149    /// rescans the music library, only error is if a rescan is already in progress.
150    #[instrument]
151    async fn library_rescan(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
152        info!("Rescanning library");
153
154        if self.library_rescan_lock.try_lock().is_err() {
155            warn!("Library rescan already in progress");
156            return Err(tonic::Status::aborted("Library rescan already in progress"));
157        }
158
159        let span = tracing::Span::current();
160
161        tokio::task::spawn(
162            async move {
163                let _guard = self.library_rescan_lock.lock().await;
164                match services::library::rescan(
165                    &self.db,
166                    &self.settings.daemon.library_paths,
167                    &self.settings.daemon.artist_separator,
168                    &self.settings.daemon.protected_artist_names,
169                    self.settings.daemon.genre_separator.as_deref(),
170                    self.settings.daemon.conflict_resolution,
171                )
172                .await
173                {
174                    Ok(()) => info!("Library rescan complete"),
175                    Err(e) => error!("Error in library_rescan: {e}"),
176                }
177
178                let result = self.publish(Event::LibraryRescanFinished).await;
179                if let Err(e) = result {
180                    error!("Error notifying clients that library_rescan_finished: {e}");
181                }
182            }
183            .instrument(span),
184        );
185
186        Ok(Response::new(()))
187    }
188    /// Check if a rescan is in progress.
189    #[instrument]
190    async fn library_rescan_in_progress(
191        self: Arc<Self>,
192        _: Request<()>,
193    ) -> TonicResult<InProgressResponse> {
194        let in_progress = self.library_rescan_lock.try_lock().is_err();
195        Ok(Response::new(InProgressResponse { in_progress }))
196    }
197    /// Analyze the music library, only error is if an analysis is already in progress.
198    #[instrument]
199    async fn library_analyze(
200        self: Arc<Self>,
201        request: Request<LibraryAnalyzeRequest>,
202    ) -> TonicResult<()> {
203        let overwrite = request.get_ref().overwrite;
204        info!("Analyzing library");
205
206        if self.library_analyze_lock.try_lock().is_err() {
207            warn!("Library analysis already in progress");
208            return Err(tonic::Status::aborted(
209                "Library analysis already in progress",
210            ));
211        }
212
213        let span = tracing::Span::current();
214
215        tokio::task::spawn(
216            async move {
217                let _guard = self.library_analyze_lock.lock().await;
218                match services::library::analyze(&self.db, self.interrupt.resubscribe(), overwrite)
219                    .await
220                {
221                    Ok(()) => info!("Library analysis complete"),
222                    Err(e) => error!("Error in library_analyze: {e}"),
223                }
224
225                let result = self.publish(Event::LibraryAnalysisFinished).await;
226                if let Err(e) = result {
227                    error!("Error notifying clients that library_analysis_finished: {e}");
228                }
229            }
230            .instrument(span),
231        );
232
233        Ok(Response::new(()))
234    }
235    /// Check if an analysis is in progress.
236    #[instrument]
237    async fn library_analyze_in_progress(
238        self: Arc<Self>,
239        _: Request<()>,
240    ) -> TonicResult<InProgressResponse> {
241        let in_progress = self.library_analyze_lock.try_lock().is_err();
242        Ok(Response::new(InProgressResponse { in_progress }))
243    }
244    /// Recluster the music library, only error is if a recluster is already in progress.
245    #[instrument]
246    async fn library_recluster(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
247        info!("Reclustering collections");
248
249        if self.collection_recluster_lock.try_lock().is_err() {
250            warn!("Collection reclustering already in progress");
251            return Err(tonic::Status::aborted(
252                "Collection reclustering already in progress",
253            ));
254        }
255
256        let span = tracing::Span::current();
257
258        tokio::task::spawn(
259            async move {
260                let _guard = self.collection_recluster_lock.lock().await;
261                match services::library::recluster(
262                    &self.db,
263                    self.settings.reclustering,
264                    self.interrupt.resubscribe(),
265                )
266                .await
267                {
268                    Ok(()) => info!("Collection reclustering complete"),
269                    Err(e) => error!("Error in library_recluster: {e}"),
270                }
271
272                let result = self.publish(Event::LibraryReclusterFinished).await;
273                if let Err(e) = result {
274                    error!("Error notifying clients that library_recluster_finished: {e}");
275                }
276            }
277            .instrument(span),
278        );
279
280        Ok(Response::new(()))
281    }
282    /// Check if a recluster is in progress.
283    #[instrument]
284    async fn library_recluster_in_progress(
285        self: Arc<Self>,
286        _: Request<()>,
287    ) -> TonicResult<InProgressResponse> {
288        let in_progress = self.collection_recluster_lock.try_lock().is_err();
289        Ok(Response::new(InProgressResponse { in_progress }))
290    }
291
292    /// Returns brief information about the music library.
293    #[instrument]
294    async fn library_brief(self: Arc<Self>, _: Request<()>) -> TonicResult<LibraryBrief> {
295        info!("Creating library brief");
296        let brief = services::library::brief(&self.db)
297            .await
298            .map_err(|e| tonic::Status::internal(format!("Error in library_brief: {e}")))?;
299        Ok(Response::new(brief))
300    }
301    /// Returns full information about the music library. (all songs, artists, albums, etc.)
302    #[instrument]
303    async fn library_full(self: Arc<Self>, _: Request<()>) -> TonicResult<LibraryFull> {
304        info!("Creating library full");
305        Ok(Response::new(
306            services::library::full(&self.db)
307                .await
308                .map_err(|e| tonic::Status::internal(format!("Error in library_full: {e}")))?,
309        ))
310    }
311    /// Returns information about the health of the music library (are there any missing files, etc.)
312    #[instrument]
313    async fn library_health(self: Arc<Self>, _: Request<()>) -> TonicResult<LibraryHealth> {
314        info!("Creating library health");
315        Ok(Response::new(
316            services::library::health(&self.db)
317                .await
318                .map_err(|e| tonic::Status::internal(format!("Error in library_health: {e}")))?,
319        ))
320    }
321
322    #[instrument]
323    async fn library_artists(self: Arc<Self>, _: Request<()>) -> TonicResult<ArtistBriefList> {
324        info!("Creating library artists brief");
325        let artists = Artist::read_all_brief(&self.db)
326            .await
327            .map_err(|e| tonic::Status::internal(format!("Error in library_artists_brief: {e}")))?
328            .into_iter()
329            .map(Into::into)
330            .collect();
331        Ok(Response::new(ArtistBriefList { artists }))
332    }
333    #[instrument]
334    async fn library_albums(self: Arc<Self>, _: Request<()>) -> TonicResult<AlbumBriefList> {
335        info!("Creating library albums brief");
336        let albums = Album::read_all_brief(&self.db)
337            .await
338            .map_err(|e| tonic::Status::internal(format!("Error in library_albums_brief: {e}")))?
339            .into_iter()
340            .map(Into::into)
341            .collect();
342        Ok(Response::new(AlbumBriefList { albums }))
343    }
344    #[instrument]
345    async fn library_songs(self: Arc<Self>, _: Request<()>) -> TonicResult<SongBriefList> {
346        info!("Creating library songs brief");
347        let songs = Song::read_all_brief(&self.db)
348            .await
349            .map_err(|e| tonic::Status::internal(format!("Error in library_songs_brief: {e}")))?
350            .into_iter()
351            .map(Into::into)
352            .collect();
353        Ok(Response::new(SongBriefList { songs }))
354    }
355    #[instrument]
356    async fn library_playlists(self: Arc<Self>, _: Request<()>) -> TonicResult<PlaylistList> {
357        info!("Creating library playlists brief");
358        let playlists = Playlist::read_all(&self.db)
359            .await
360            .map_err(|e| tonic::Status::internal(format!("Error in library_playlists_brief: {e}")))?
361            .into_iter()
362            .map(Into::into)
363            .collect();
364        Ok(Response::new(PlaylistList { playlists }))
365    }
366    #[instrument]
367    async fn library_collections(self: Arc<Self>, _: Request<()>) -> TonicResult<CollectionList> {
368        info!("Creating library collections brief");
369        let collections = Collection::read_all(&self.db)
370            .await
371            .map_err(|e| {
372                tonic::Status::internal(format!("Error in library_collections_brief: {e}"))
373            })?
374            .into_iter()
375            .map(Into::into)
376            .collect();
377        Ok(Response::new(CollectionList { collections }))
378    }
379    #[instrument]
380    async fn library_dynamic_playlists(
381        self: Arc<Self>,
382        _: Request<()>,
383    ) -> TonicResult<DynamicPlaylistList> {
384        info!("Creating library dynamic playlists full");
385        let playlists = DynamicPlaylist::read_all(&self.db)
386            .await
387            .map_err(|e| {
388                tonic::Status::internal(format!("Error in library_dynamic_playlists_full: {e}"))
389            })?
390            .into_iter()
391            .map(Into::into)
392            .collect();
393        Ok(Response::new(DynamicPlaylistList { playlists }))
394    }
395
396    /// Get a song by its ID.
397    #[instrument]
398    async fn library_song_get(self: Arc<Self>, request: Request<Ulid>) -> TonicResult<SongOption> {
399        let id = (schemas::song::TABLE_NAME, request.into_inner().id).into();
400        info!("Getting song by ID: {id}");
401        let song = Song::read(&self.db, id)
402            .await
403            .map_err(|e| tonic::Status::internal(format!("Error in library_song_get: {e}")))?
404            .map(Into::into);
405        Ok(Response::new(SongOption { song }))
406    }
407    /// Get a song by its file path.
408    #[instrument]
409    async fn library_song_get_by_path(
410        self: Arc<Self>,
411        request: Request<Path>,
412    ) -> TonicResult<SongOption> {
413        let path = request.into_inner().path;
414        let path = PathBuf::from(path)
415            .canonicalize()
416            .map_err(|e| tonic::Status::invalid_argument(format!("Invalid path provided: {e}")))?;
417        info!("Getting song by path: {}", path.display());
418        let song = Song::read_by_path(&self.db, path)
419            .await
420            .map_err(|e| {
421                tonic::Status::internal(format!("Error in library_song_get_by_path: {e}"))
422            })?
423            .map(Into::into);
424        Ok(Response::new(SongOption { song }))
425    }
426    /// Get the artists of a song.
427    #[instrument]
428    async fn library_song_get_artists(
429        self: Arc<Self>,
430        request: Request<Ulid>,
431    ) -> TonicResult<ArtistBriefList> {
432        let id = (schemas::song::TABLE_NAME, request.into_inner().id).into();
433        info!("Getting artist of: {id}");
434        let artists = Song::read_artist(&self.db, id)
435            .await
436            .map_err(|e| tonic::Status::internal(format!("Error in library_song_get_artist: {e}")))?
437            .into_iter()
438            .map(Into::into)
439            .collect();
440        Ok(Response::new(ArtistBriefList { artists }))
441    }
442    #[instrument]
443    async fn library_song_get_album(
444        self: Arc<Self>,
445        request: Request<Ulid>,
446    ) -> TonicResult<AlbumBriefOption> {
447        let song_id = (schemas::song::TABLE_NAME, request.into_inner().id).into();
448        info!("Resolving album for song {song_id}");
449        let album = Song::read_album(&self.db, song_id)
450            .await
451            .map_err(|e| tonic::Status::internal(format!("library_song_get_album failed: {e}")))?
452            .map(Into::into);
453        Ok(Response::new(AlbumBriefOption { album }))
454    }
455    #[instrument]
456    async fn library_song_get_playlists(
457        self: Arc<Self>,
458        request: Request<Ulid>,
459    ) -> TonicResult<PlaylistList> {
460        let song_id = (schemas::song::TABLE_NAME, request.into_inner().id).into();
461        info!("Collecting playlists containing {song_id}");
462        let playlists = Song::read_playlists(&self.db, song_id)
463            .await
464            .map_err(|e| {
465                tonic::Status::internal(format!("library_song_get_playlists failed: {e}"))
466            })?
467            .into_iter()
468            .map(Into::into)
469            .collect();
470        Ok(Response::new(PlaylistList { playlists }))
471    }
472    #[instrument]
473    async fn library_song_get_collections(
474        self: Arc<Self>,
475        request: Request<Ulid>,
476    ) -> TonicResult<CollectionList> {
477        let song_id = (schemas::song::TABLE_NAME, request.into_inner().id).into();
478        info!("Collecting collections containing {song_id}");
479        let collections = Song::read_collections(&self.db, song_id)
480            .await
481            .map_err(|e| {
482                tonic::Status::internal(format!("library_song_get_collections failed: {e}"))
483            })?
484            .into_iter()
485            .map(Into::into)
486            .collect();
487        Ok(Response::new(CollectionList { collections }))
488    }
489    #[instrument]
490    async fn library_album_get(
491        self: Arc<Self>,
492        request: Request<Ulid>,
493    ) -> TonicResult<AlbumOption> {
494        let album_id = (schemas::album::TABLE_NAME, request.into_inner().id).into();
495        info!("Fetching album {album_id}");
496        let album = Album::read(&self.db, album_id)
497            .await
498            .map_err(|e| tonic::Status::internal(format!("library_album_get failed: {e}")))?
499            .map(Into::into);
500        Ok(Response::new(AlbumOption { album }))
501    }
502    #[instrument]
503    async fn library_album_get_artists(
504        self: Arc<Self>,
505        request: Request<Ulid>,
506    ) -> TonicResult<ArtistBriefList> {
507        let album_id = (schemas::album::TABLE_NAME, request.into_inner().id).into();
508        info!("Fetching contributors for album {album_id}");
509        let artists = Album::read_artist(&self.db, album_id)
510            .await
511            .map_err(|e| tonic::Status::internal(format!("library_album_get_artist failed: {e}")))?
512            .into_iter()
513            .map(Into::into)
514            .collect();
515        Ok(Response::new(ArtistBriefList { artists }))
516    }
517    #[instrument]
518    async fn library_album_get_songs(
519        self: Arc<Self>,
520        request: Request<Ulid>,
521    ) -> TonicResult<SongBriefList> {
522        let album_id = (schemas::album::TABLE_NAME, request.into_inner().id).into();
523        info!("Listing songs for album {album_id}");
524        let songs = Album::read_songs(&self.db, album_id)
525            .await
526            .map_err(|e| tonic::Status::internal(format!("library_album_get_songs failed: {e}")))?
527            .into_iter()
528            .map(Into::into)
529            .collect();
530        Ok(Response::new(SongBriefList { songs }))
531    }
532    #[instrument]
533    async fn library_artist_get(
534        self: Arc<Self>,
535        request: Request<Ulid>,
536    ) -> TonicResult<ArtistOption> {
537        let artist_id = (schemas::artist::TABLE_NAME, request.into_inner().id).into();
538        info!("Fetching artist {artist_id}");
539        let artist = Artist::read(&self.db, artist_id)
540            .await
541            .map_err(|e| tonic::Status::internal(format!("library_artist_get failed: {e}")))?
542            .map(Into::into);
543        Ok(Response::new(ArtistOption { artist }))
544    }
545    #[instrument]
546    async fn library_artist_get_songs(
547        self: Arc<Self>,
548        request: Request<Ulid>,
549    ) -> TonicResult<SongBriefList> {
550        let artist_id = (schemas::artist::TABLE_NAME, request.into_inner().id).into();
551        info!("Listing songs for artist {artist_id}");
552        let songs = Artist::read_songs(&self.db, artist_id)
553            .await
554            .map_err(|e| tonic::Status::internal(format!("library_artist_get_songs failed: {e}")))?
555            .into_iter()
556            .map(Into::into)
557            .collect();
558        Ok(Response::new(SongBriefList { songs }))
559    }
560    #[instrument]
561    async fn library_artist_get_albums(
562        self: Arc<Self>,
563        request: Request<Ulid>,
564    ) -> TonicResult<AlbumBriefList> {
565        let artist_id = (schemas::artist::TABLE_NAME, request.into_inner().id).into();
566        info!("Listing albums for artist {artist_id}");
567        let albums = Artist::read_albums(&self.db, artist_id)
568            .await
569            .map_err(|e| tonic::Status::internal(format!("library_artist_get_albums failed: {e}")))?
570            .into_iter()
571            .map(Into::into)
572            .collect();
573        Ok(Response::new(AlbumBriefList { albums }))
574    }
575    #[instrument]
576    async fn library_playlist_get(
577        self: Arc<Self>,
578        request: Request<Ulid>,
579    ) -> TonicResult<PlaylistOption> {
580        let playlist_id = (schemas::playlist::TABLE_NAME, request.into_inner().id).into();
581        info!("Fetching playlist {playlist_id}");
582        let playlist = Playlist::read(&self.db, playlist_id)
583            .await
584            .map_err(|e| tonic::Status::internal(format!("library_playlist_get failed: {e}")))?
585            .map(Into::into);
586        Ok(Response::new(PlaylistOption { playlist }))
587    }
588    #[instrument]
589    async fn library_playlist_get_by_name(
590        self: Arc<Self>,
591        request: Request<PlaylistName>,
592    ) -> TonicResult<PlaylistOption> {
593        let name = request.into_inner().name;
594        info!("Fetching playlist by name: {name}");
595        let playlist = Playlist::read_by_name(&self.db, name)
596            .await
597            .map_err(|e| {
598                tonic::Status::internal(format!("library_playlist_get_by_name failed: {e}"))
599            })?
600            .map(Into::into);
601        Ok(Response::new(PlaylistOption { playlist }))
602    }
603    #[instrument]
604    async fn library_playlist_get_songs(
605        self: Arc<Self>,
606        request: Request<Ulid>,
607    ) -> TonicResult<SongBriefList> {
608        let playlist_id = (schemas::playlist::TABLE_NAME, request.into_inner().id).into();
609        info!("Listing songs for playlist {playlist_id}");
610        let songs = Playlist::read_songs(&self.db, playlist_id)
611            .await
612            .map_err(|e| {
613                tonic::Status::internal(format!("library_playlist_get_songs failed: {e}"))
614            })?
615            .into_iter()
616            .map(Into::into)
617            .collect();
618        Ok(Response::new(SongBriefList { songs }))
619    }
620    #[instrument]
621    async fn library_collection_get(
622        self: Arc<Self>,
623        request: Request<Ulid>,
624    ) -> TonicResult<CollectionOption> {
625        let collection_id = (schemas::collection::TABLE_NAME, request.into_inner().id).into();
626        info!("Fetching collection {collection_id}");
627        let collection = Collection::read(&self.db, collection_id)
628            .await
629            .map_err(|e| tonic::Status::internal(format!("library_collection_get failed: {e}")))?
630            .map(Into::into);
631        Ok(Response::new(CollectionOption { collection }))
632    }
633    #[instrument]
634    async fn library_collection_get_songs(
635        self: Arc<Self>,
636        request: Request<Ulid>,
637    ) -> TonicResult<SongBriefList> {
638        let collection_id = (schemas::collection::TABLE_NAME, request.into_inner().id).into();
639        info!("Listing songs for collection {collection_id}");
640        let songs = Collection::read_songs(&self.db, collection_id)
641            .await
642            .map_err(|e| {
643                tonic::Status::internal(format!("library_collection_get_songs failed: {e}"))
644            })?
645            .into_iter()
646            .map(Into::into)
647            .collect();
648        Ok(Response::new(SongBriefList { songs }))
649    }
650    #[instrument]
651    async fn library_dynamic_playlist_get(
652        self: Arc<Self>,
653        request: Request<Ulid>,
654    ) -> TonicResult<DynamicPlaylistOption> {
655        let dynamic_playlist_id = (schemas::dynamic::TABLE_NAME, request.into_inner().id).into();
656        info!("Fetching dynamic playlist {dynamic_playlist_id}");
657        let playlist = DynamicPlaylist::read(&self.db, dynamic_playlist_id)
658            .await
659            .map_err(|e| {
660                tonic::Status::internal(format!("library_dynamic_playlist_get failed: {e}"))
661            })?
662            .map(Into::into);
663        Ok(Response::new(DynamicPlaylistOption { playlist }))
664    }
665    #[instrument]
666    async fn library_dynamic_playlist_get_songs(
667        self: Arc<Self>,
668        request: Request<Ulid>,
669    ) -> TonicResult<SongBriefList> {
670        let dynamic_playlist_id = (schemas::dynamic::TABLE_NAME, request.into_inner().id).into();
671        info!("Listing songs for dynamic playlist {dynamic_playlist_id}");
672        let songs = DynamicPlaylist::run_query_by_id(&self.db, dynamic_playlist_id)
673            .await
674            .map_err(|e| {
675                tonic::Status::internal(format!("library_dynamic_playlist_get_songs failed: {e}"))
676            })?
677            .ok_or_else(|| tonic::Status::not_found("dynamic playlist not found"))?
678            .into_iter()
679            .map(Into::into)
680            .collect();
681        Ok(Response::new(SongBriefList { songs }))
682    }
683
684    #[instrument]
685    async fn state_audio(self: Arc<Self>, _: Request<()>) -> TonicResult<StateAudioResponse> {
686        debug!("Getting state of audio player");
687        let (tx, rx) = tokio::sync::oneshot::channel();
688
689        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
690
691        let state = rx
692            .await
693            .inspect_err(|e| warn!("Error in state_audio: {e}"))
694            .ok()
695            .map(Into::into);
696
697        Ok(Response::new(StateAudioResponse { state }))
698    }
699    #[instrument]
700    async fn current_artists(
701        self: Arc<Self>,
702        _: Request<()>,
703    ) -> TonicResult<ArtistBriefListOption> {
704        info!("Fetching current song artists");
705        let (tx, rx) = tokio::sync::oneshot::channel();
706
707        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
708
709        if let Some(song) = rx
710            .await
711            .inspect_err(|e| warn!("Error in current_artists: {e}"))
712            .ok()
713            .and_then(|s| s.current_song)
714        {
715            let artists = Song::read_artist(&self.db, song.id)
716                .await
717                .map_err(|e| {
718                    tonic::Status::not_found(format!("Error finding artists of current song: {e}"))
719                })?
720                .into_iter()
721                .map(Into::into)
722                .collect();
723            let artists = ArtistBriefList { artists };
724            Ok(Response::new(ArtistBriefListOption {
725                artists: Some(artists),
726            }))
727        } else {
728            Ok(Response::new(ArtistBriefListOption { artists: None }))
729        }
730    }
731    #[instrument]
732    async fn current_album(self: Arc<Self>, _: Request<()>) -> TonicResult<AlbumBriefOption> {
733        info!("Fetching current song album");
734        let (tx, rx) = tokio::sync::oneshot::channel();
735
736        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
737
738        if let Some(song) = rx
739            .await
740            .inspect_err(|e| warn!("Error in current_album: {e}"))
741            .ok()
742            .and_then(|s| s.current_song)
743        {
744            let album = Song::read_album(&self.db, song.id)
745                .await
746                .map_err(|e| {
747                    tonic::Status::not_found(format!("Error finding album of current song: {e}"))
748                })?
749                .map(Into::into);
750            Ok(Response::new(AlbumBriefOption { album }))
751        } else {
752            Ok(Response::new(AlbumBriefOption { album: None }))
753        }
754    }
755    #[instrument]
756    async fn current_song(self: Arc<Self>, _: Request<()>) -> TonicResult<SongBriefOption> {
757        info!("Fetching current song");
758        let (tx, rx) = tokio::sync::oneshot::channel();
759
760        self.audio_kernel.send(AudioCommand::ReportStatus(tx));
761        let song = rx.await.ok().and_then(|s| s.current_song).map(Into::into);
762        Ok(Response::new(SongBriefOption { song }))
763    }
764
765    /// Get a random artist
766    #[instrument]
767    async fn rand_artist(self: Arc<Self>, _: Request<()>) -> TonicResult<ArtistBriefOption> {
768        info!("Getting random artist");
769        let artist = Artist::read_rand(&self.db, 1)
770            .await
771            .map_err(|e| tonic::Status::internal(format!("rand_artist failed: {e}")))?
772            .first()
773            .cloned()
774            .map(Into::into);
775        Ok(Response::new(ArtistBriefOption { artist }))
776    }
777    /// Get a random album
778    #[instrument]
779    async fn rand_album(self: Arc<Self>, _: Request<()>) -> TonicResult<AlbumBriefOption> {
780        info!("Getting random album");
781        let album = Album::read_rand(&self.db, 1)
782            .await
783            .map_err(|e| tonic::Status::internal(format!("rand_album failed: {e}")))?
784            .first()
785            .cloned()
786            .map(Into::into);
787        Ok(Response::new(AlbumBriefOption { album }))
788    }
789    /// Get a random song
790    #[instrument]
791    async fn rand_song(self: Arc<Self>, _: Request<()>) -> TonicResult<SongBriefOption> {
792        info!("Getting random song");
793        let song = Song::read_rand(&self.db, 1)
794            .await
795            .map_err(|e| tonic::Status::internal(format!("rand_song failed: {e}")))?
796            .first()
797            .cloned()
798            .map(Into::into);
799        Ok(Response::new(SongBriefOption { song }))
800    }
801
802    /// returns a list of artists, albums, and songs matching the given search query.
803    async fn search(self: Arc<Self>, request: Request<SearchRequest>) -> TonicResult<SearchResult> {
804        let SearchRequest { query, limit } = request.into_inner();
805        info!("Searching for: {query}");
806        // basic idea:
807        // 1. search for songs
808        // 2. search for albums
809        // 3. search for artists
810        // 4. return the results
811        let songs = Song::search(
812            &self.db,
813            &query,
814            usize::try_from(limit).unwrap_or(usize::MAX),
815        )
816        .await
817        .inspect_err(|e| warn!("Error in search: {e}"))
818        .unwrap_or_default()
819        .into_iter()
820        .map(Into::into)
821        .collect();
822
823        let albums = Album::search(
824            &self.db,
825            &query,
826            usize::try_from(limit).unwrap_or(usize::MAX),
827        )
828        .await
829        .inspect_err(|e| warn!("Error in search: {e}"))
830        .unwrap_or_default()
831        .into_iter()
832        .map(Into::into)
833        .collect();
834
835        let artists = Artist::search(
836            &self.db,
837            &query,
838            usize::try_from(limit).unwrap_or(usize::MAX),
839        )
840        .await
841        .inspect_err(|e| warn!("Error in search: {e}"))
842        .unwrap_or_default()
843        .into_iter()
844        .map(Into::into)
845        .collect();
846        Ok(Response::new(SearchResult {
847            songs,
848            albums,
849            artists,
850        }))
851    }
852    /// returns a list of artists matching the given search query.
853    #[instrument]
854    async fn search_artist(
855        self: Arc<Self>,
856        request: Request<SearchRequest>,
857    ) -> TonicResult<ArtistBriefList> {
858        let SearchRequest { query, limit } = request.into_inner();
859        info!("Searching for artist: {query}");
860        let artists = Artist::search(
861            &self.db,
862            &query,
863            usize::try_from(limit).unwrap_or(usize::MAX),
864        )
865        .await
866        .inspect_err(|e| {
867            warn!("Error in search_artist: {e}");
868        })
869        .unwrap_or_default()
870        .into_iter()
871        .map(Into::into)
872        .collect();
873        Ok(Response::new(ArtistBriefList { artists }))
874    }
875    /// returns a list of albums matching the given search query.
876    #[instrument]
877    async fn search_album(
878        self: Arc<Self>,
879        request: Request<SearchRequest>,
880    ) -> TonicResult<AlbumBriefList> {
881        let SearchRequest { query, limit } = request.into_inner();
882        info!("Searching for album: {query}");
883        let albums = Album::search(
884            &self.db,
885            &query,
886            usize::try_from(limit).unwrap_or(usize::MAX),
887        )
888        .await
889        .inspect_err(|e| {
890            warn!("Error in search_album: {e}");
891        })
892        .unwrap_or_default()
893        .into_iter()
894        .map(Into::into)
895        .collect();
896        Ok(Response::new(AlbumBriefList { albums }))
897    }
898    /// returns a list of songs matching the given search query.
899    #[instrument]
900    async fn search_song(
901        self: Arc<Self>,
902        request: Request<SearchRequest>,
903    ) -> TonicResult<SongBriefList> {
904        let SearchRequest { query, limit } = request.into_inner();
905        info!("Searching for song: {query}");
906        let songs = Song::search(
907            &self.db,
908            &query,
909            usize::try_from(limit).unwrap_or(usize::MAX),
910        )
911        .await
912        .inspect_err(|e| {
913            warn!("Error in search_song: {e}");
914        })
915        .unwrap_or_default()
916        .into_iter()
917        .map(Into::into)
918        .collect();
919        Ok(Response::new(SongBriefList { songs }))
920    }
921
922    /// toggles playback (play/pause)
923    #[instrument]
924    async fn playback_toggle(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
925        info!("Toggling playback");
926        self.audio_kernel.send(AudioCommand::TogglePlayback);
927        Ok(Response::new(()))
928    }
929    /// starts playback (unpause).
930    #[instrument]
931    async fn playback_play(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
932        info!("Playing");
933        self.audio_kernel.send(AudioCommand::Play);
934        Ok(Response::new(()))
935    }
936    /// pause playback.
937    #[instrument]
938    async fn playback_pause(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
939        info!("Pausing playback");
940        self.audio_kernel.send(AudioCommand::Pause);
941        Ok(Response::new(()))
942    }
943    /// stop playback.
944    #[instrument]
945    async fn playback_stop(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
946        info!("Stopping playback");
947        self.audio_kernel.send(AudioCommand::Stop);
948        Ok(Response::new(()))
949    }
950    /// restart the current song.
951    #[instrument]
952    async fn playback_restart(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
953        info!("Restarting current song");
954        self.audio_kernel.send(AudioCommand::RestartSong);
955        Ok(Response::new(()))
956    }
957    /// skip forward by the given amount of songs
958    #[instrument]
959    async fn playback_skip_forward(
960        self: Arc<Self>,
961        request: Request<PlaybackSkipRequest>,
962    ) -> TonicResult<()> {
963        let PlaybackSkipRequest { amount } = request.into_inner();
964        info!("Skipping forward by {amount} songs");
965        self.audio_kernel
966            .send(AudioCommand::Queue(QueueCommand::SkipForward(
967                usize::try_from(amount).unwrap_or(usize::MAX),
968            )));
969        Ok(Response::new(()))
970    }
971    /// go backwards by the given amount of songs.
972    #[instrument]
973    async fn playback_skip_backward(
974        self: Arc<Self>,
975        request: Request<PlaybackSkipRequest>,
976    ) -> TonicResult<()> {
977        let PlaybackSkipRequest { amount } = request.into_inner();
978        info!("Going back by {amount} songs");
979        self.audio_kernel
980            .send(AudioCommand::Queue(QueueCommand::SkipBackward(
981                usize::try_from(amount).unwrap_or(usize::MAX),
982            )));
983        Ok(Response::new(()))
984    }
985    /// stop playback.
986    /// (clears the queue and stops playback)
987    #[instrument]
988    async fn playback_clear_player(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
989        info!("Stopping playback");
990        self.audio_kernel.send(AudioCommand::ClearPlayer);
991        Ok(Response::new(()))
992    }
993    /// clear the queue.
994    #[instrument]
995    async fn playback_clear(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
996        info!("Clearing queue and stopping playback");
997        self.audio_kernel
998            .send(AudioCommand::Queue(QueueCommand::Clear));
999        Ok(Response::new(()))
1000    }
1001    /// seek forwards, backwards, or to an absolute second in the current song.
1002    #[instrument]
1003    async fn playback_seek(
1004        self: Arc<Self>,
1005        request: Request<PlaybackSeekRequest>,
1006    ) -> TonicResult<()> {
1007        let PlaybackSeekRequest { seek, duration } = request.into_inner();
1008        let duration: Duration = duration.normalized().try_into().map_err(|e| {
1009            tonic::Status::invalid_argument(format!("Invalid duration provided: {e}"))
1010        })?;
1011        let seek: SeekType = mecomp_prost::SeekType::try_from(seek)
1012            .map_err(|e| tonic::Status::invalid_argument(e.to_string()))?
1013            .into();
1014        info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
1015        self.audio_kernel.send(AudioCommand::Seek(seek, duration));
1016        Ok(Response::new(()))
1017    }
1018    /// set the repeat mode.
1019    #[instrument]
1020    async fn playback_repeat(
1021        self: Arc<Self>,
1022        request: Request<PlaybackRepeatRequest>,
1023    ) -> TonicResult<()> {
1024        let PlaybackRepeatRequest { mode } = request.into_inner();
1025        let mode: RepeatMode = mecomp_prost::RepeatMode::try_from(mode)
1026            .map_err(|e| tonic::Status::invalid_argument(e.to_string()))?
1027            .into();
1028        info!("Setting repeat mode to: {mode}");
1029        self.audio_kernel
1030            .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
1031        Ok(Response::new(()))
1032    }
1033    /// Shuffle the current queue, then start playing from the 1st Song in the queue.
1034    #[instrument]
1035    async fn playback_shuffle(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
1036        info!("Shuffling queue");
1037        self.audio_kernel
1038            .send(AudioCommand::Queue(QueueCommand::Shuffle));
1039        Ok(Response::new(()))
1040    }
1041    /// set the volume to the given value
1042    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will multiply each sample by this value.
1043    #[instrument]
1044    async fn playback_volume(
1045        self: Arc<Self>,
1046        request: Request<PlaybackVolumeSetRequest>,
1047    ) -> TonicResult<()> {
1048        let PlaybackVolumeSetRequest { volume } = request.into_inner();
1049        info!("Setting volume to: {volume}",);
1050        self.audio_kernel
1051            .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
1052        Ok(Response::new(()))
1053    }
1054    /// increase the volume by the given amount
1055    #[instrument]
1056    async fn playback_volume_up(
1057        self: Arc<Self>,
1058        request: Request<PlaybackVolumeAdjustRequest>,
1059    ) -> TonicResult<()> {
1060        let PlaybackVolumeAdjustRequest { amount } = request.into_inner();
1061        info!("Increasing volume by: {amount}",);
1062        self.audio_kernel
1063            .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
1064        Ok(Response::new(()))
1065    }
1066    /// decrease the volume by the given amount
1067    #[instrument]
1068    async fn playback_volume_down(
1069        self: Arc<Self>,
1070        request: Request<PlaybackVolumeAdjustRequest>,
1071    ) -> TonicResult<()> {
1072        let PlaybackVolumeAdjustRequest { amount } = request.into_inner();
1073        info!("Decreasing volume by: {amount}",);
1074        self.audio_kernel
1075            .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
1076        Ok(Response::new(()))
1077    }
1078    /// toggle the volume mute.
1079    #[instrument]
1080    async fn playback_toggle_mute(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
1081        info!("Toggling volume mute");
1082        self.audio_kernel
1083            .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
1084        Ok(Response::new(()))
1085    }
1086    /// mute the volume.
1087    #[instrument]
1088    async fn playback_mute(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
1089        info!("Muting volume");
1090        self.audio_kernel
1091            .send(AudioCommand::Volume(VolumeCommand::Mute));
1092        Ok(Response::new(()))
1093    }
1094    /// unmute the volume.
1095    #[instrument]
1096    async fn playback_unmute(self: Arc<Self>, _: Request<()>) -> TonicResult<()> {
1097        info!("Unmuting volume");
1098        self.audio_kernel
1099            .send(AudioCommand::Volume(VolumeCommand::Unmute));
1100        Ok(Response::new(()))
1101    }
1102
1103    /// add a song to the queue.
1104    /// (if the queue is empty, it will start playing the song.)
1105    #[instrument]
1106    async fn queue_add(self: Arc<Self>, request: Request<RecordId>) -> TonicResult<()> {
1107        let thing = request.into_inner().into();
1108        info!("Adding thing to queue: {thing}");
1109
1110        let songs = services::get_songs_from_things(&self.db, &[thing])
1111            .await
1112            .map_err(|e| {
1113                tonic::Status::internal(format!("Error getting songs from provided things: {e}"))
1114            })?;
1115
1116        if songs.is_empty() {
1117            return Err(tonic::Status::not_found("No songs found"));
1118        }
1119
1120        self.audio_kernel
1121            .send(AudioCommand::Queue(QueueCommand::AddToQueue(
1122                songs.into_iter().map(Into::into).collect(),
1123            )));
1124
1125        Ok(Response::new(()))
1126    }
1127    /// add a list of things to the queue.
1128    /// (if the queue is empty, it will start playing the first thing in the list.)
1129    #[instrument]
1130    async fn queue_add_list(self: Arc<Self>, request: Request<RecordIdList>) -> TonicResult<()> {
1131        let RecordIdList { ids } = request.into_inner();
1132        let list = ids.into_iter().map(Into::into).collect::<Vec<_>>();
1133
1134        info!(
1135            "Adding list to queue: ({})",
1136            list.iter()
1137                .map(ToString::to_string)
1138                .collect::<Vec<_>>()
1139                .join(", ")
1140        );
1141
1142        // go through the list, and get songs for each thing (depending on what it is)
1143        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list)
1144            .await
1145            .map_err(|e| {
1146                tonic::Status::internal(format!("Error getting songs from provided things: {e}"))
1147            })?;
1148
1149        self.audio_kernel
1150            .send(AudioCommand::Queue(QueueCommand::AddToQueue(
1151                songs.into_iter().map(Into::into).collect(),
1152            )));
1153
1154        Ok(Response::new(()))
1155    }
1156    /// set the current song to a queue index.
1157    /// if the index is out of bounds, it will be clamped to the nearest valid index.
1158    #[instrument]
1159    async fn queue_set_index(
1160        self: Arc<Self>,
1161        request: Request<QueueSetIndexRequest>,
1162    ) -> TonicResult<()> {
1163        let QueueSetIndexRequest { index } = request.into_inner();
1164        info!("Setting queue index to: {index}");
1165
1166        self.audio_kernel
1167            .send(AudioCommand::Queue(QueueCommand::SetPosition(
1168                usize::try_from(index).unwrap_or(usize::MAX),
1169            )));
1170        Ok(Response::new(()))
1171    }
1172    /// remove a range of songs from the queue.
1173    /// if the range is out of bounds, it will be clamped to the nearest valid range.
1174    #[instrument]
1175    async fn queue_remove_range(
1176        self: Arc<Self>,
1177        request: Request<QueueRemoveRangeRequest>,
1178    ) -> TonicResult<()> {
1179        let QueueRemoveRangeRequest { start, end } = request.into_inner();
1180        let start = usize::try_from(start).map_err(|e| {
1181            tonic::Status::invalid_argument(format!("Invalid start index for range: {e}"))
1182        })?;
1183        let end = usize::try_from(end).map_err(|e| {
1184            tonic::Status::invalid_argument(format!("Invalid end index for range: {e}"))
1185        })?;
1186        let range = start..end;
1187        info!("Removing queue range: {range:?}");
1188
1189        self.audio_kernel
1190            .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
1191        Ok(Response::new(()))
1192    }
1193
1194    /// create a new playlist.
1195    /// if a playlist with the same name already exists, this will return that playlist's id in the error variant
1196    #[instrument]
1197    async fn playlist_get_or_create(
1198        self: Arc<Self>,
1199        request: Request<PlaylistName>,
1200    ) -> TonicResult<RecordId> {
1201        let PlaylistName { name } = request.into_inner();
1202        info!("Creating new playlist: {name}");
1203
1204        // see if a playlist with that name already exists
1205        match Playlist::read_by_name(&self.db, name.clone()).await {
1206            Ok(Some(playlist)) => {
1207                return Ok(Response::new(RecordId::new(
1208                    playlist.id.table(),
1209                    playlist.id.key(),
1210                )));
1211            }
1212            Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
1213            _ => {}
1214        }
1215        // if it doesn't, create a new playlist with that name
1216        match Playlist::create(
1217            &self.db,
1218            Playlist {
1219                id: Playlist::generate_id(),
1220                name,
1221                runtime: Duration::from_secs(0),
1222                song_count: 0,
1223            },
1224        )
1225        .await
1226        .map_err(|e| {
1227            tonic::Status::internal(format!(
1228                "Error in playlist_new (creating new playlist): {e}"
1229            ))
1230        })? {
1231            Some(playlist) => Ok(Response::new(RecordId::new(
1232                playlist.id.table(),
1233                playlist.id.key(),
1234            ))),
1235            None => Err(tonic::Status::not_found("playlist was not created")),
1236        }
1237    }
1238    /// remove a playlist.
1239    #[instrument]
1240    async fn playlist_remove(self: Arc<Self>, request: Request<Ulid>) -> TonicResult<()> {
1241        let id = (schemas::playlist::TABLE_NAME, request.into_inner().id).into();
1242        info!("Removing playlist with id: {id}");
1243        Playlist::delete(&self.db, id)
1244            .await
1245            .map_err(|e| tonic::Status::internal(format!("failed to delete playlist, {e}")))?
1246            .ok_or_else(|| tonic::Status::not_found("playlist was not found"))?;
1247        Ok(Response::new(()))
1248    }
1249    /// clone a playlist.
1250    /// (creates a new playlist with the same name (append " (copy)") and contents as the given playlist.)
1251    /// returns the id of the new playlist
1252    #[instrument]
1253    async fn playlist_clone(self: Arc<Self>, request: Request<Ulid>) -> TonicResult<RecordId> {
1254        let id = (schemas::playlist::TABLE_NAME, request.into_inner().id).into();
1255        info!("Cloning playlist with id: {id}");
1256
1257        let new = Playlist::create_copy(&self.db, id)
1258            .await
1259            .map_err(|e| tonic::Status::internal(format!("failed to clone playlist, {e}")))?
1260            .ok_or_else(|| tonic::Status::not_found("playlist was not found"))?;
1261
1262        Ok(Response::new(RecordId::new(new.id.table(), new.id.key())))
1263    }
1264    /// remove a list of songs from a playlist.
1265    /// if the songs are not in the playlist, this will do nothing.
1266    #[instrument]
1267    async fn playlist_remove_songs(
1268        self: Arc<Self>,
1269        request: Request<PlaylistRemoveSongsRequest>,
1270    ) -> TonicResult<()> {
1271        let PlaylistRemoveSongsRequest {
1272            playlist_id,
1273            song_ids,
1274        } = request.into_inner();
1275        let playlist = (schemas::playlist::TABLE_NAME, playlist_id.id).into();
1276        let songs = song_ids
1277            .into_iter()
1278            .map(|id| (schemas::song::TABLE_NAME, id.id).into())
1279            .collect::<Vec<_>>();
1280        info!("Removing song from playlist: {playlist} ({songs:?})");
1281
1282        Playlist::remove_songs(&self.db, playlist, songs)
1283            .await
1284            .map_err(|e| {
1285                tonic::Status::internal(format!("failed to remove songs from playlist, {e}"))
1286            })?;
1287        Ok(Response::new(()))
1288    }
1289    /// Add a thing to a playlist.
1290    /// If the thing is something that has songs (an album, artist, etc.), it will add all the songs.
1291    #[instrument]
1292    async fn playlist_add(
1293        self: Arc<Self>,
1294        request: Request<PlaylistAddRequest>,
1295    ) -> TonicResult<()> {
1296        let PlaylistAddRequest {
1297            playlist_id,
1298            record_id,
1299        } = request.into_inner();
1300        let playlist = (schemas::playlist::TABLE_NAME, playlist_id.id).into();
1301        let thing = record_id.into();
1302        info!("Adding thing to playlist: {playlist} ({thing})");
1303
1304        // get songs for the thing
1305        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing])
1306            .await
1307            .map_err(|e| {
1308                tonic::Status::internal(format!("failed to get songs from things, {e}"))
1309            })?;
1310
1311        Playlist::add_songs(
1312            &self.db,
1313            playlist,
1314            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1315        )
1316        .await
1317        .map_err(|e| tonic::Status::internal(format!("Error adding things to playlist: {e}")))?;
1318        Ok(Response::new(()))
1319    }
1320    /// Add a list of things to a playlist.
1321    /// If the things are something that have songs (an album, artist, etc.), it will add all the songs.
1322    #[instrument]
1323    async fn playlist_add_list(
1324        self: Arc<Self>,
1325        request: Request<PlaylistAddListRequest>,
1326    ) -> TonicResult<()> {
1327        let PlaylistAddListRequest {
1328            playlist_id,
1329            record_ids,
1330        } = request.into_inner();
1331        let playlist = (schemas::playlist::TABLE_NAME, playlist_id.id).into();
1332        let list = record_ids.into_iter().map(Into::into).collect::<Vec<_>>();
1333        info!(
1334            "Adding list to playlist: {playlist} ({})",
1335            list.iter()
1336                .map(ToString::to_string)
1337                .collect::<Vec<_>>()
1338                .join(", ")
1339        );
1340
1341        // go through the list, and get songs for each thing (depending on what it is)
1342        let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list)
1343            .await
1344            .map_err(|e| {
1345                tonic::Status::internal(format!("failed to get songs from things, {e}"))
1346            })?;
1347
1348        Playlist::add_songs(
1349            &self.db,
1350            playlist,
1351            songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1352        )
1353        .await
1354        .map_err(|e| tonic::Status::internal(format!("failed to add songs to playlist, {e}")))?;
1355        Ok(Response::new(()))
1356    }
1357    /// Rename a playlist.
1358    #[instrument]
1359    async fn playlist_rename(
1360        self: Arc<Self>,
1361        request: Request<PlaylistRenameRequest>,
1362    ) -> TonicResult<PlaylistBrief> {
1363        let PlaylistRenameRequest { playlist_id, name } = request.into_inner();
1364        let id = (schemas::playlist::TABLE_NAME, playlist_id.id).into();
1365        info!("Renaming playlist: {id} ({name})");
1366        let updated = Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1367            .await
1368            .map_err(|e| tonic::Status::internal(format!("failed to rename playlist, {e}")))?
1369            .ok_or_else(|| tonic::Status::not_found("playlist not found"))?;
1370        Ok(Response::new(updated.into()))
1371    }
1372    /// Export a playlist to a .m3u file
1373    #[instrument]
1374    async fn playlist_export(
1375        self: Arc<Self>,
1376        request: Request<PlaylistExportRequest>,
1377    ) -> TonicResult<()> {
1378        let PlaylistExportRequest { playlist_id, path } = request.into_inner();
1379        let id = (schemas::playlist::TABLE_NAME, playlist_id.id).into();
1380        info!("Exporting playlist to: {path}");
1381
1382        // validate the path
1383        validate_file_path(&path, "m3u", false)
1384            .map_err(|e| tonic::Status::invalid_argument(format!("invalid file path: {e}")))?;
1385
1386        // read the playlist
1387        let playlist = Playlist::read(&self.db, id)
1388            .await
1389            .inspect_err(|e| warn!("Error in playlist_export: {e}"))
1390            .ok()
1391            .flatten()
1392            .ok_or_else(|| tonic::Status::not_found("playlist not found"))?;
1393        // get the songs in the playlist
1394        let songs = Playlist::read_songs(&self.db, playlist.id)
1395            .await
1396            .inspect_err(|e| warn!("Error in playlist_export: {e}"))
1397            .ok()
1398            .unwrap_or_default();
1399
1400        // create the file
1401        let file = File::create(&path).inspect_err(|e| warn!("Error in playlist_export: {e}"))?;
1402        // write the playlist to the file
1403        export_playlist(&playlist.name, &songs, file)
1404            .inspect_err(|e| warn!("Error in playlist_export: {e}"))
1405            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1406        info!("Exported playlist to: {path}");
1407        Ok(Response::new(()))
1408    }
1409    /// Import a playlist from a .m3u file
1410    #[instrument]
1411    async fn playlist_import(
1412        self: Arc<Self>,
1413        request: Request<PlaylistImportRequest>,
1414    ) -> TonicResult<RecordId> {
1415        let PlaylistImportRequest { path, name } = request.into_inner();
1416
1417        info!("Importing playlist from: {path}");
1418
1419        // validate the path
1420        validate_file_path(&path, "m3u", true)
1421            .map_err(|e| tonic::Status::invalid_argument(format!("invalid file path: {e}")))?;
1422
1423        // read file
1424        let file = File::open(&path).inspect_err(|e| warn!("Error in playlist_import: {e}"))?;
1425        let (parsed_name, song_paths) = import_playlist(file)
1426            .inspect_err(|e| warn!("Error in playlist_import: {e}"))
1427            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1428
1429        log::debug!("Parsed playlist name: {parsed_name:?}");
1430        log::debug!("Parsed song paths: {song_paths:?}");
1431
1432        let name = match (name, parsed_name) {
1433            (Some(name), _) | (None, Some(name)) => name,
1434            (None, None) => "Imported Playlist".to_owned(),
1435        };
1436
1437        // check if the playlist already exists
1438        if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1439            // if it does, return the id
1440            info!("Playlist \"{name}\" already exists, will not import");
1441            return Ok(Response::new(RecordId::new(
1442                playlist.id.table(),
1443                playlist.id.key(),
1444            )));
1445        }
1446
1447        // create the playlist
1448        let playlist = Playlist::create(
1449            &self.db,
1450            Playlist {
1451                id: Playlist::generate_id(),
1452                name,
1453                runtime: Duration::from_secs(0),
1454                song_count: 0,
1455            },
1456        )
1457        .await
1458        .inspect_err(|e| warn!("Error in playlist_import: {e}"))
1459        .map_err(|e| tonic::Status::internal(e.to_string()))?
1460        .ok_or_else(|| tonic::Status::not_found("failed to create playlist"))?;
1461
1462        // lookup all the songs
1463        let mut songs = Vec::new();
1464        for path in &song_paths {
1465            let Some(song) = Song::read_by_path(&self.db, path.clone())
1466                .await
1467                .inspect_err(|e| warn!("Error in playlist_import: {e}"))
1468                .map_err(|e| tonic::Status::internal(e.to_string()))?
1469            else {
1470                warn!("Song at {} not found in the library", path.display());
1471                continue;
1472            };
1473
1474            songs.push(song.id);
1475        }
1476
1477        if songs.is_empty() {
1478            return Err(tonic::Status::new(
1479                Code::InvalidArgument,
1480                BackupError::NoValidSongs(song_paths.len()).to_string(),
1481            ));
1482        }
1483
1484        // add the songs to the playlist
1485        Playlist::add_songs(&self.db, playlist.id.clone(), songs)
1486            .await
1487            .inspect_err(|e| {
1488                warn!("Error in playlist_import: {e}");
1489            })
1490            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1491
1492        // return the playlist id
1493        Ok(Response::new(RecordId::new(
1494            playlist.id.table(),
1495            playlist.id.key(),
1496        )))
1497    }
1498
1499    /// Collections: freeze a collection (convert it to a playlist).
1500    #[instrument]
1501    async fn collection_freeze(
1502        self: Arc<Self>,
1503        request: Request<CollectionFreezeRequest>,
1504    ) -> TonicResult<RecordId> {
1505        let CollectionFreezeRequest { id, name } = request.into_inner();
1506        let id = (schemas::collection::TABLE_NAME, id.id).into();
1507        info!("Freezing collection: {id:?} ({name})");
1508        let playlist = Collection::freeze(&self.db, id, name)
1509            .await
1510            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1511        Ok(Response::new(RecordId::new(
1512            playlist.id.table(),
1513            playlist.id.key(),
1514        )))
1515    }
1516
1517    /// Radio: get the `n` most similar songs to the given things.
1518    #[instrument]
1519    async fn radio_get_similar(
1520        self: Arc<Self>,
1521        request: Request<RadioSimilarRequest>,
1522    ) -> TonicResult<SongBriefList> {
1523        let RadioSimilarRequest { record_ids, limit } = request.into_inner();
1524        let things = record_ids.into_iter().map(Into::into).collect();
1525        info!("Getting the {limit} most similar songs to: {things:?}");
1526        let songs = services::radio::get_similar(&self.db, things, limit)
1527            .await
1528            .inspect_err(|e| warn!("Error in radio_get_similar: {e}"))
1529            .map_err(|e| tonic::Status::internal(e.to_string()))?
1530            .into_iter()
1531            .map(|s| s.brief().into())
1532            .collect();
1533        Ok(Response::new(SongBriefList { songs }))
1534    }
1535    /// Radio: get the ids of the `n` most similar songs to the given things.
1536    #[instrument]
1537    async fn radio_get_similar_ids(
1538        self: Arc<Self>,
1539        request: Request<RadioSimilarRequest>,
1540    ) -> TonicResult<RecordIdList> {
1541        let RadioSimilarRequest { record_ids, limit } = request.into_inner();
1542        let things = record_ids.into_iter().map(Into::into).collect();
1543        info!("Getting the {limit} most similar songs to: {things:?}");
1544        let ids = services::radio::get_similar(&self.db, things, limit)
1545            .await
1546            .inspect_err(|e| warn!("Error in radio_get_similar_songs: {e}"))
1547            .map_err(|e| tonic::Status::internal(e.to_string()))?
1548            .into_iter()
1549            .map(|song| RecordId::new(song.id.table(), song.id.key()))
1550            .collect();
1551        Ok(Response::new(RecordIdList::new(ids)))
1552    }
1553
1554    /// Dynamic Playlists: create a new DP with the given name and query
1555    #[instrument]
1556    async fn dynamic_playlist_create(
1557        self: Arc<Self>,
1558        request: Request<DynamicPlaylistCreateRequest>,
1559    ) -> TonicResult<RecordId> {
1560        let DynamicPlaylistCreateRequest { name, query } = request.into_inner();
1561        let query = query
1562            .parse::<Query>()
1563            .map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
1564        let id = DynamicPlaylist::generate_id();
1565        info!("Creating new DP: {id:?} ({name})");
1566
1567        match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1568            .await
1569            .inspect_err(|e| warn!("Error in dynamic_playlist_create: {e}"))
1570            .map_err(|e| tonic::Status::internal(e.to_string()))?
1571        {
1572            Some(dp) => Ok(Response::new(RecordId::new(dp.id.table(), dp.id.key()))),
1573            None => Err(tonic::Status::not_found(
1574                "failed to create dynamic playlist",
1575            )),
1576        }
1577    }
1578    /// Dynamic Playlists: update a DP
1579    #[instrument]
1580    async fn dynamic_playlist_update(
1581        self: Arc<Self>,
1582        request: Request<DynamicPlaylistUpdateRequest>,
1583    ) -> TonicResult<mecomp_prost::DynamicPlaylist> {
1584        let DynamicPlaylistUpdateRequest { id, changes } = request.into_inner();
1585        let query = if let Some(new_query) = changes.new_query {
1586            Some(
1587                new_query
1588                    .parse::<Query>()
1589                    .map_err(|e| tonic::Status::invalid_argument(e.to_string()))?,
1590            )
1591        } else {
1592            None
1593        };
1594        let id = (schemas::dynamic::TABLE_NAME, id.id).into();
1595        let changes = DynamicPlaylistChangeSet {
1596            name: changes.new_name,
1597            query,
1598        };
1599        info!("Updating DP: {id:?}, {changes:?}");
1600        let updated = DynamicPlaylist::update(&self.db, id, changes)
1601            .await
1602            .inspect_err(|e| warn!("Error in dynamic_playlist_update: {e}"))
1603            .map_err(|e| tonic::Status::internal(e.to_string()))?
1604            .ok_or_else(|| tonic::Status::not_found("Dynamic Playlist not found"))?
1605            .into();
1606        Ok(Response::new(updated))
1607    }
1608    /// Dynamic Playlists: remove a DP
1609    #[instrument]
1610    async fn dynamic_playlist_remove(self: Arc<Self>, request: Request<Ulid>) -> TonicResult<()> {
1611        let id = (schemas::dynamic::TABLE_NAME, request.into_inner().id).into();
1612        info!("Removing DP with id: {id:?}");
1613        DynamicPlaylist::delete(&self.db, id)
1614            .await
1615            .map_err(|e| tonic::Status::internal(e.to_string()))?
1616            .ok_or_else(|| tonic::Status::not_found("Dynamic Playlist not found"))?;
1617        Ok(Response::new(()))
1618    }
1619    /// Dynamic Playlists: export dynamic playlists to a csv file
1620    #[instrument]
1621    async fn dynamic_playlist_export(
1622        self: Arc<Self>,
1623        request: Request<mecomp_prost::Path>,
1624    ) -> TonicResult<()> {
1625        let path = request.into_inner().path;
1626        info!("Exporting dynamic playlists to: {path}");
1627
1628        // validate the path
1629        validate_file_path(&path, "csv", false)
1630            .map_err(|e| tonic::Status::invalid_argument(format!("Backup Error: {e}")))?;
1631
1632        // read the playlists
1633        let playlists = DynamicPlaylist::read_all(&self.db)
1634            .await
1635            .inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))
1636            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1637
1638        // create the file
1639        let file =
1640            File::create(&path).inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1641        let writer = csv::Writer::from_writer(std::io::BufWriter::new(file));
1642        // write the playlists to the file
1643        export_dynamic_playlists(&playlists, writer)
1644            .inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))
1645            .map_err(|e| tonic::Status::internal(e.to_string()))?;
1646        info!("Exported dynamic playlists to: {path}");
1647        Ok(Response::new(()))
1648    }
1649    /// Dynamic Playlists: import dynamic playlists from a csv file
1650    #[instrument]
1651    async fn dynamic_playlist_import(
1652        self: Arc<Self>,
1653        request: Request<mecomp_prost::Path>,
1654    ) -> TonicResult<DynamicPlaylistList> {
1655        let path = request.into_inner().path;
1656        info!("Importing dynamic playlists from: {path}");
1657
1658        // validate the path
1659        validate_file_path(&path, "csv", true)
1660            .map_err(|e| tonic::Status::invalid_argument(format!("Backup Error: {e}")))?;
1661
1662        // read file
1663        let file =
1664            File::open(&path).inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1665        let reader = csv::ReaderBuilder::new()
1666            .has_headers(true)
1667            .from_reader(std::io::BufReader::new(file));
1668
1669        // read the playlists from the file
1670        let playlists = import_dynamic_playlists(reader)
1671            .inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))
1672            .map_err(|e| tonic::Status::internal(format!("Backup Error: {e}")))?;
1673
1674        if playlists.is_empty() {
1675            return Err(tonic::Status::new(
1676                Code::InvalidArgument,
1677                format!("Backup Error: {}", BackupError::NoValidPlaylists),
1678            ));
1679        }
1680
1681        // create the playlists
1682        let mut ids = Vec::new();
1683        for playlist in playlists {
1684            // if a playlist with the same name already exists, skip this one
1685            if let Ok(Some(existing_playlist)) =
1686                DynamicPlaylist::read_by_name(&self.db, playlist.name.clone()).await
1687            {
1688                info!(
1689                    "Dynamic Playlist \"{}\" already exists, will not import",
1690                    existing_playlist.name
1691                );
1692                continue;
1693            }
1694
1695            ids.push(
1696                DynamicPlaylist::create(&self.db, playlist)
1697                    .await
1698                    .inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))
1699                    .map_err(|e| tonic::Status::internal(e.to_string()))?
1700                    .ok_or_else(|| tonic::Status::internal("Failed to create Dynamic Playlist"))?
1701                    .into(),
1702            );
1703        }
1704
1705        Ok(Response::new(DynamicPlaylistList { playlists: ids }))
1706    }
1707}