1use std::{fs::File, path::PathBuf, sync::Arc, time::Duration};
3use 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};
10use 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 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 #[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 #[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 #[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 #[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 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 #[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 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 #[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 #[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_file_path(&path, "m3u", false)
1396 .map_err(|e| tonic::Status::invalid_argument(format!("invalid file path: {e}")))?;
1397
1398 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 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 let file = File::create(&path).inspect_err(|e| warn!("Error in playlist_export: {e}"))?;
1414 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 #[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_file_path(&path, "m3u", true)
1433 .map_err(|e| tonic::Status::invalid_argument(format!("invalid file path: {e}")))?;
1434
1435 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 if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1451 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 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 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 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 Ok(Response::new(RecordId::new(
1506 playlist.id.table(),
1507 playlist.id.key(),
1508 )))
1509 }
1510
1511 #[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 #[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 #[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 #[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 #[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 #[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 #[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_file_path(&path, "csv", false)
1642 .map_err(|e| tonic::Status::invalid_argument(format!("Backup Error: {e}")))?;
1643
1644 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 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 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 #[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_file_path(&path, "csv", true)
1672 .map_err(|e| tonic::Status::invalid_argument(format!("Backup Error: {e}")))?;
1673
1674 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 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 let mut ids = Vec::new();
1695 for playlist in playlists {
1696 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}