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