Skip to main content

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