1use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
3use ::tarpc::context::Context;
5use log::{debug, error, info, warn};
6use rand::seq::SliceRandom;
7use surrealdb::{engine::local::Db, Surreal};
8use tap::TapFallible;
9use tokio::sync::{Mutex, RwLock};
10use tracing::{instrument, Instrument};
11use mecomp_core::{
13 audio::{
14 commands::{AudioCommand, QueueCommand, VolumeCommand},
15 AudioKernelSender,
16 },
17 config::Settings,
18 errors::SerializableLibraryError,
19 rpc::{
20 AlbumId, ArtistId, CollectionId, DynamicPlaylistId, MusicPlayer, PlaylistId, SearchResult,
21 SongId,
22 },
23 state::{
24 library::{LibraryBrief, LibraryFull, LibraryHealth},
25 RepeatMode, SeekType, StateAudio,
26 },
27 udp::{Event, Message, Sender},
28};
29use mecomp_storage::{
30 db::schemas::{
31 self,
32 album::{Album, AlbumBrief},
33 artist::{Artist, ArtistBrief},
34 collection::{Collection, CollectionBrief},
35 dynamic::{query::Query, DynamicPlaylist, DynamicPlaylistChangeSet},
36 playlist::{Playlist, PlaylistBrief, PlaylistChangeSet},
37 song::{Song, SongBrief},
38 },
39 errors::Error,
40};
41use one_or_many::OneOrMany;
42
43use crate::services;
44
45#[derive(Clone, Debug)]
46pub struct MusicPlayerServer {
47 db: Arc<Surreal<Db>>,
48 settings: Arc<Settings>,
49 audio_kernel: Arc<AudioKernelSender>,
50 library_rescan_lock: Arc<Mutex<()>>,
51 library_analyze_lock: Arc<Mutex<()>>,
52 collection_recluster_lock: Arc<Mutex<()>>,
53 publisher: Arc<RwLock<Sender<Message>>>,
54}
55
56impl MusicPlayerServer {
57 #[must_use]
58 #[inline]
59 pub fn new(
60 db: Arc<Surreal<Db>>,
61 settings: Arc<Settings>,
62 audio_kernel: Arc<AudioKernelSender>,
63 event_publisher: Arc<RwLock<Sender<Message>>>,
64 ) -> Self {
65 Self {
66 db,
67 publisher: event_publisher,
68 settings,
69 audio_kernel,
70 library_rescan_lock: Arc::new(Mutex::new(())),
71 library_analyze_lock: Arc::new(Mutex::new(())),
72 collection_recluster_lock: Arc::new(Mutex::new(())),
73 }
74 }
75
76 #[instrument]
82 pub async fn publish(
83 &self,
84 message: impl Into<Message> + Send + Sync + std::fmt::Debug,
85 ) -> Result<(), mecomp_core::errors::UdpError> {
86 self.publisher.read().await.send(message).await
87 }
88}
89
90#[allow(clippy::missing_inline_in_public_items)]
91impl MusicPlayer for MusicPlayerServer {
92 #[instrument]
93 async fn register_listener(self, context: Context, listener_addr: std::net::SocketAddr) {
94 info!("Registering listener: {listener_addr}");
95 self.publisher.write().await.add_subscriber(listener_addr);
96 }
97
98 async fn ping(self, _: Context) -> String {
99 "pong".to_string()
100 }
101
102 #[instrument]
104 async fn library_rescan(self, context: Context) -> Result<(), SerializableLibraryError> {
105 info!("Rescanning library");
106
107 if self.library_rescan_lock.try_lock().is_err() {
108 warn!("Library rescan already in progress");
109 return Err(SerializableLibraryError::RescanInProgress);
110 }
111
112 let span = tracing::Span::current();
113
114 std::thread::Builder::new()
115 .name(String::from("Library Rescan"))
116 .spawn(move || {
117 futures::executor::block_on(
118 async {
119 let _guard = self.library_rescan_lock.lock().await;
120 match services::library::rescan(
121 &self.db,
122 &self.settings.daemon.library_paths,
123 &self.settings.daemon.artist_separator,
124 self.settings.daemon.genre_separator.as_deref(),
125 self.settings.daemon.conflict_resolution,
126 )
127 .await
128 {
129 Ok(()) => info!("Library rescan complete"),
130 Err(e) => error!("Error in library_rescan: {e}"),
131 }
132
133 let result = self.publish(Event::LibraryRescanFinished).await;
134 if let Err(e) = result {
135 error!("Error notifying clients that library_rescan_finished: {e}");
136 }
137 }
138 .instrument(span),
139 );
140 })?;
141
142 Ok(())
143 }
144 #[instrument]
146 async fn library_rescan_in_progress(self, context: Context) -> bool {
147 self.library_rescan_lock.try_lock().is_err()
148 }
149 #[instrument]
151 async fn library_analyze(
152 self,
153 context: Context,
154 overwrite: bool,
155 ) -> Result<(), SerializableLibraryError> {
156 #[cfg(not(feature = "analysis"))]
157 {
158 warn!("Analysis is not enabled");
159 return Err(SerializableLibraryError::AnalysisNotEnabled);
160 }
161
162 #[cfg(feature = "analysis")]
163 {
164 info!("Analyzing library");
165
166 if self.library_analyze_lock.try_lock().is_err() {
167 warn!("Library analysis already in progress");
168 return Err(SerializableLibraryError::AnalysisInProgress);
169 }
170 let span = tracing::Span::current();
171
172 std::thread::Builder::new()
173 .name(String::from("Library Analysis"))
174 .spawn(move || {
175 futures::executor::block_on(
176 async {
177 let _guard = self.library_analyze_lock.lock().await;
178 match services::library::analyze(&self.db, overwrite).await {
179 Ok(()) => info!("Library analysis complete"),
180 Err(e) => error!("Error in library_analyze: {e}"),
181 }
182
183 let result = &self.publish(Event::LibraryAnalysisFinished).await;
184 if let Err(e) = result {
185 error!(
186 "Error notifying clients that library_analysis_finished: {e}"
187 );
188 }
189 }
190 .instrument(span),
191 );
192 })?;
193
194 Ok(())
195 }
196 }
197 #[instrument]
199 async fn library_analyze_in_progress(self, context: Context) -> bool {
200 self.library_analyze_lock.try_lock().is_err()
201 }
202 #[instrument]
204 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
205 #[cfg(not(feature = "analysis"))]
206 {
207 warn!("Analysis is not enabled");
208 return Err(SerializableLibraryError::AnalysisNotEnabled);
209 }
210
211 #[cfg(feature = "analysis")]
212 {
213 info!("Reclustering collections");
214
215 if self.collection_recluster_lock.try_lock().is_err() {
216 warn!("Collection reclustering already in progress");
217 return Err(SerializableLibraryError::ReclusterInProgress);
218 }
219
220 let span = tracing::Span::current();
221
222 std::thread::Builder::new()
223 .name(String::from("Collection Recluster"))
224 .spawn(move || {
225 futures::executor::block_on(
226 async {
227 let _guard = self.collection_recluster_lock.lock().await;
228 match services::library::recluster(
229 &self.db,
230 &self.settings.reclustering,
231 )
232 .await
233 {
234 Ok(()) => info!("Collection reclustering complete"),
235 Err(e) => error!("Error in collection_recluster: {e}"),
236 }
237
238 let result = &self.publish(Event::LibraryReclusterFinished).await;
239 if let Err(e) = result {
240 error!(
241 "Error notifying clients that library_recluster_finished: {e}"
242 );
243 }
244 }
245 .instrument(span),
246 );
247 })?;
248
249 Ok(())
250 }
251 }
252 #[instrument]
254 async fn library_recluster_in_progress(self, context: Context) -> bool {
255 self.collection_recluster_lock.try_lock().is_err()
256 }
257 #[instrument]
259 async fn library_brief(
260 self,
261 context: Context,
262 ) -> Result<LibraryBrief, SerializableLibraryError> {
263 info!("Creating library brief");
264 Ok(services::library::brief(&self.db)
265 .await
266 .tap_err(|e| warn!("Error in library_brief: {e}"))?)
267 }
268 #[instrument]
270 async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
271 info!("Creating library full");
272 Ok(services::library::full(&self.db)
273 .await
274 .tap_err(|e| warn!("Error in library_full: {e}"))?)
275 }
276 #[instrument]
278 async fn library_artists_brief(
279 self,
280 context: Context,
281 ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
282 info!("Creating library artists brief");
283 Ok(Artist::read_all(&self.db)
284 .await
285 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
286 .iter()
287 .map(std::convert::Into::into)
288 .collect())
289 }
290 #[instrument]
292 async fn library_artists_full(
293 self,
294 context: Context,
295 ) -> Result<Box<[Artist]>, SerializableLibraryError> {
296 info!("Creating library artists full");
297 Ok(Artist::read_all(&self.db)
298 .await
299 .map(std::vec::Vec::into_boxed_slice)
300 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?)
301 }
302 #[instrument]
304 async fn library_albums_brief(
305 self,
306 context: Context,
307 ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
308 info!("Creating library albums brief");
309 Ok(Album::read_all(&self.db)
310 .await
311 .tap_err(|e| warn!("Error in library_albums_brief: {e}"))?
312 .iter()
313 .map(std::convert::Into::into)
314 .collect())
315 }
316 #[instrument]
318 async fn library_albums_full(
319 self,
320 context: Context,
321 ) -> Result<Box<[Album]>, SerializableLibraryError> {
322 info!("Creating library albums full");
323 Ok(Album::read_all(&self.db)
324 .await
325 .map(std::vec::Vec::into_boxed_slice)
326 .tap_err(|e| warn!("Error in library_albums_full: {e}"))?)
327 }
328 #[instrument]
330 async fn library_songs_brief(
331 self,
332 context: Context,
333 ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
334 info!("Creating library songs brief");
335 Ok(Song::read_all(&self.db)
336 .await
337 .tap_err(|e| warn!("Error in library_songs_brief: {e}"))?
338 .iter()
339 .map(std::convert::Into::into)
340 .collect())
341 }
342 #[instrument]
344 async fn library_songs_full(
345 self,
346 context: Context,
347 ) -> Result<Box<[Song]>, SerializableLibraryError> {
348 info!("Creating library songs full");
349 Ok(Song::read_all(&self.db)
350 .await
351 .map(std::vec::Vec::into_boxed_slice)
352 .tap_err(|e| warn!("Error in library_songs_full: {e}"))?)
353 }
354 #[instrument]
356 async fn library_health(
357 self,
358 context: Context,
359 ) -> Result<LibraryHealth, SerializableLibraryError> {
360 info!("Creating library health");
361 Ok(services::library::health(&self.db)
362 .await
363 .tap_err(|e| warn!("Error in library_health: {e}"))?)
364 }
365 #[instrument]
367 async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
368 let id = id.into();
369 info!("Getting song by ID: {id}");
370 Song::read(&self.db, id)
371 .await
372 .tap_err(|e| warn!("Error in library_song_get: {e}"))
373 .ok()
374 .flatten()
375 }
376 #[instrument]
378 async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
379 info!("Getting song by path: {}", path.display());
380 Song::read_by_path(&self.db, path)
381 .await
382 .tap_err(|e| warn!("Error in library_song_get_by_path: {e}"))
383 .ok()
384 .flatten()
385 }
386 #[instrument]
388 async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
389 let id = id.into();
390 info!("Getting artist of: {id}");
391 Song::read_artist(&self.db, id)
392 .await
393 .tap_err(|e| warn!("Error in library_song_get_artist: {e}"))
394 .ok()
395 .into()
396 }
397 #[instrument]
399 async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
400 let id = id.into();
401 info!("Getting album of: {id}");
402 Song::read_album(&self.db, id)
403 .await
404 .tap_err(|e| warn!("Error in library_song_get_album: {e}"))
405 .ok()
406 .flatten()
407 }
408 #[instrument]
410 async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
411 let id = id.into();
412 info!("Getting playlists of: {id}");
413 Song::read_playlists(&self.db, id)
414 .await
415 .tap_err(|e| warn!("Error in library_song_get_playlists: {e}"))
416 .ok()
417 .unwrap_or_default()
418 .into()
419 }
420 #[instrument]
422 async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
423 let id = id.into();
424 info!("Getting collections of: {id}");
425 Song::read_collections(&self.db, id)
426 .await
427 .tap_err(|e| warn!("Error in library_song_get_collections: {e}"))
428 .ok()
429 .unwrap_or_default()
430 .into()
431 }
432
433 #[instrument]
435 async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
436 let id = id.into();
437 info!("Getting album by ID: {id}");
438 Album::read(&self.db, id)
439 .await
440 .tap_err(|e| warn!("Error in library_album_get: {e}"))
441 .ok()
442 .flatten()
443 }
444 #[instrument]
446 async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
447 let id = id.into();
448 info!("Getting artists of: {id}");
449 Album::read_artist(&self.db, id)
450 .await
451 .tap_err(|e| warn!("Error in library_album_get_artist: {e}"))
452 .ok()
453 .into()
454 }
455 #[instrument]
457 async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
458 let id = id.into();
459 info!("Getting songs of: {id}");
460 Album::read_songs(&self.db, id)
461 .await
462 .tap_err(|e| warn!("Error in library_album_get_songs: {e}"))
463 .ok()
464 .map(Into::into)
465 }
466 #[instrument]
468 async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
469 let id = id.into();
470 info!("Getting artist by ID: {id}");
471 Artist::read(&self.db, id)
472 .await
473 .tap_err(|e| warn!("Error in library_artist_get: {e}"))
474 .ok()
475 .flatten()
476 }
477 #[instrument]
479 async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
480 let id = id.into();
481 info!("Getting songs of: {id}");
482 Artist::read_songs(&self.db, id)
483 .await
484 .tap_err(|e| warn!("Error in library_artist_get_songs: {e}"))
485 .ok()
486 .map(Into::into)
487 }
488 #[instrument]
490 async fn library_artist_get_albums(
491 self,
492 context: Context,
493 id: ArtistId,
494 ) -> Option<Box<[Album]>> {
495 let id = id.into();
496 info!("Getting albums of: {id}");
497 Artist::read_albums(&self.db, id)
498 .await
499 .tap_err(|e| warn!("Error in library_artist_get_albums: {e}"))
500 .ok()
501 .map(Into::into)
502 }
503
504 #[instrument]
506 async fn daemon_shutdown(self, context: Context) {
507 let publisher = self.publisher.clone();
508 let audio_kernel = self.audio_kernel.clone();
509 std::thread::Builder::new()
510 .name(String::from("Daemon Shutdown"))
511 .spawn(move || {
512 std::thread::sleep(std::time::Duration::from_secs(1));
513 let _ = futures::executor::block_on(
514 publisher.blocking_read().send(Event::DaemonShutdown),
515 );
516 audio_kernel.send(AudioCommand::Exit);
517 std::process::exit(0);
518 })
519 .unwrap();
520 info!("Shutting down daemon in 1 second");
521 }
522
523 #[instrument]
525 async fn state_audio(self, context: Context) -> Option<StateAudio> {
526 debug!("Getting state of audio player");
527 let (tx, rx) = tokio::sync::oneshot::channel();
528
529 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
530
531 rx.await
532 .tap_err(|e| warn!("Error in state_audio: {e}"))
533 .ok()
534 }
535
536 #[instrument]
538 async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
539 info!("Getting current artist");
540 let (tx, rx) = tokio::sync::oneshot::channel();
541
542 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
543
544 if let Some(song) = rx
545 .await
546 .tap_err(|e| warn!("Error in current_artist: {e}"))
547 .ok()
548 .and_then(|state| state.current_song)
549 {
550 Song::read_artist(&self.db, song.id)
551 .await
552 .tap_err(|e| warn!("Error in current_album: {e}"))
553 .ok()
554 .into()
555 } else {
556 OneOrMany::None
557 }
558 }
559 #[instrument]
561 async fn current_album(self, context: Context) -> Option<Album> {
562 info!("Getting current album");
563 let (tx, rx) = tokio::sync::oneshot::channel();
564
565 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
566
567 if let Some(song) = rx
568 .await
569 .tap_err(|e| warn!("Error in current_album: {e}"))
570 .ok()
571 .and_then(|state| state.current_song)
572 {
573 Song::read_album(&self.db, song.id)
574 .await
575 .tap_err(|e| warn!("Error in current_album: {e}"))
576 .ok()
577 .flatten()
578 } else {
579 None
580 }
581 }
582 #[instrument]
584 async fn current_song(self, context: Context) -> Option<Song> {
585 info!("Getting current song");
586 let (tx, rx) = tokio::sync::oneshot::channel();
587
588 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
589
590 rx.await
591 .tap_err(|e| warn!("Error in current_song: {e}"))
592 .ok()
593 .and_then(|state| state.current_song)
594 }
595
596 #[instrument]
598 async fn rand_artist(self, context: Context) -> Option<Artist> {
599 info!("Getting random artist");
600 Artist::read_all(&self.db)
601 .await
602 .tap_err(|e| warn!("Error in rand_artist: {e}"))
603 .ok()
604 .and_then(|artists| artists.choose(&mut rand::thread_rng()).cloned())
605 }
606 #[instrument]
608 async fn rand_album(self, context: Context) -> Option<Album> {
609 info!("Getting random album");
610 Album::read_all(&self.db)
611 .await
612 .tap_err(|e| warn!("Error in rand_album: {e}"))
613 .ok()
614 .and_then(|albums| albums.choose(&mut rand::thread_rng()).cloned())
615 }
616 #[instrument]
618 async fn rand_song(self, context: Context) -> Option<Song> {
619 info!("Getting random song");
620 Song::read_all(&self.db)
621 .await
622 .tap_err(|e| warn!("Error in rand_song: {e}"))
623 .ok()
624 .and_then(|songs| songs.choose(&mut rand::thread_rng()).cloned())
625 }
626
627 #[instrument]
629 async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
630 info!("Searching for: {query}");
631 let songs = Song::search(&self.db, &query, i64::from(limit))
637 .await
638 .tap_err(|e| warn!("Error in search: {e}"))
639 .unwrap_or_default()
640 .into();
641
642 let albums = Album::search(&self.db, &query, i64::from(limit))
643 .await
644 .tap_err(|e| warn!("Error in search: {e}"))
645 .unwrap_or_default()
646 .into();
647
648 let artists = Artist::search(&self.db, &query, i64::from(limit))
649 .await
650 .tap_err(|e| warn!("Error in search: {e}"))
651 .unwrap_or_default()
652 .into();
653 SearchResult {
654 songs,
655 albums,
656 artists,
657 }
658 }
659 #[instrument]
661 async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
662 info!("Searching for artist: {query}");
663 Artist::search(&self.db, &query, i64::from(limit))
664 .await
665 .tap_err(|e| {
666 warn!("Error in search_artist: {e}");
667 })
668 .unwrap_or_default()
669 .into()
670 }
671 #[instrument]
673 async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
674 info!("Searching for album: {query}");
675 Album::search(&self.db, &query, i64::from(limit))
676 .await
677 .tap_err(|e| {
678 warn!("Error in search_album: {e}");
679 })
680 .unwrap_or_default()
681 .into()
682 }
683 #[instrument]
685 async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
686 info!("Searching for song: {query}");
687 Song::search(&self.db, &query, i64::from(limit))
688 .await
689 .tap_err(|e| {
690 warn!("Error in search_song: {e}");
691 })
692 .unwrap_or_default()
693 .into()
694 }
695
696 #[instrument]
698 async fn playback_toggle(self, context: Context) {
699 info!("Toggling playback");
700 self.audio_kernel.send(AudioCommand::TogglePlayback);
701 }
702 #[instrument]
704 async fn playback_play(self, context: Context) {
705 info!("Starting playback");
706 self.audio_kernel.send(AudioCommand::Play);
707 }
708 #[instrument]
710 async fn playback_pause(self, context: Context) {
711 info!("Pausing playback");
712 self.audio_kernel.send(AudioCommand::Pause);
713 }
714 #[instrument]
716 async fn playback_stop(self, context: Context) {
717 info!("Stopping playback");
718 self.audio_kernel.send(AudioCommand::Stop);
719 }
720 #[instrument]
722 async fn playback_restart(self, context: Context) {
723 info!("Restarting current song");
724 self.audio_kernel.send(AudioCommand::RestartSong);
725 }
726 #[instrument]
728 async fn playback_skip_forward(self, context: Context, amount: usize) {
729 info!("Skipping forward by {amount} songs");
730 self.audio_kernel
731 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
732 }
733 #[instrument]
735 async fn playback_skip_backward(self, context: Context, amount: usize) {
736 info!("Going back by {amount} songs");
737 self.audio_kernel
738 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
739 }
740 #[instrument]
743 async fn playback_clear_player(self, context: Context) {
744 info!("Stopping playback");
745 self.audio_kernel.send(AudioCommand::ClearPlayer);
746 }
747 #[instrument]
749 async fn playback_clear(self, context: Context) {
750 info!("Clearing queue and stopping playback");
751 self.audio_kernel
752 .send(AudioCommand::Queue(QueueCommand::Clear));
753 }
754 #[instrument]
756 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
757 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
758 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
759 }
760 #[instrument]
762 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
763 info!("Setting repeat mode to: {}", mode);
764 self.audio_kernel
765 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
766 }
767 #[instrument]
769 async fn playback_shuffle(self, context: Context) {
770 info!("Shuffling queue");
771 self.audio_kernel
772 .send(AudioCommand::Queue(QueueCommand::Shuffle));
773 }
774 #[instrument]
777 async fn playback_volume(self, context: Context, volume: f32) {
778 info!("Setting volume to: {volume}",);
779 self.audio_kernel
780 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
781 }
782 #[instrument]
784 async fn playback_volume_up(self, context: Context, amount: f32) {
785 info!("Increasing volume by: {amount}",);
786 self.audio_kernel
787 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
788 }
789 #[instrument]
791 async fn playback_volume_down(self, context: Context, amount: f32) {
792 info!("Decreasing volume by: {amount}",);
793 self.audio_kernel
794 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
795 }
796 #[instrument]
798 async fn playback_volume_toggle_mute(self, context: Context) {
799 info!("Toggling volume mute");
800 self.audio_kernel
801 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
802 }
803 #[instrument]
805 async fn playback_mute(self, context: Context) {
806 info!("Muting volume");
807 self.audio_kernel
808 .send(AudioCommand::Volume(VolumeCommand::Mute));
809 }
810 #[instrument]
812 async fn playback_unmute(self, context: Context) {
813 info!("Unmuting volume");
814 self.audio_kernel
815 .send(AudioCommand::Volume(VolumeCommand::Unmute));
816 }
817
818 #[instrument]
821 async fn queue_add(
822 self,
823 context: Context,
824 thing: schemas::RecordId,
825 ) -> Result<(), SerializableLibraryError> {
826 info!("Adding thing to queue: {thing}");
827
828 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
829
830 if songs.is_empty() {
831 return Err(Error::NotFound.into());
832 }
833
834 self.audio_kernel
835 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
836 songs,
837 ))));
838
839 Ok(())
840 }
841 #[instrument]
844 async fn queue_add_list(
845 self,
846 context: Context,
847 list: Vec<schemas::RecordId>,
848 ) -> Result<(), SerializableLibraryError> {
849 info!(
850 "Adding list to queue: ({})",
851 list.iter()
852 .map(ToString::to_string)
853 .collect::<Vec<_>>()
854 .join(", ")
855 );
856
857 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
859
860 self.audio_kernel
861 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
862 songs,
863 ))));
864
865 Ok(())
866 }
867 #[instrument]
870 async fn queue_set_index(self, context: Context, index: usize) {
871 info!("Setting queue index to: {index}");
872
873 self.audio_kernel
874 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
875 }
876 #[instrument]
879 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
880 info!("Removing queue range: {range:?}");
881
882 self.audio_kernel
883 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
884 }
885
886 #[instrument]
888 async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
889 info!("Listing playlists");
890 Playlist::read_all(&self.db)
891 .await
892 .tap_err(|e| warn!("Error in playlist_list: {e}"))
893 .ok()
894 .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
895 .unwrap_or_default()
896 }
897 #[instrument]
900 async fn playlist_get_or_create(
901 self,
902 context: Context,
903 name: String,
904 ) -> Result<PlaylistId, SerializableLibraryError> {
905 info!("Creating new playlist: {name}");
906
907 match Playlist::read_by_name(&self.db, name.clone()).await {
909 Ok(Some(playlist)) => return Ok(playlist.id.into()),
910 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
911 _ => {}
912 }
913 match Playlist::create(
915 &self.db,
916 Playlist {
917 id: Playlist::generate_id(),
918 name,
919 runtime: Duration::from_secs(0),
920 song_count: 0,
921 },
922 )
923 .await
924 .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
925 {
926 Some(playlist) => Ok(playlist.id.into()),
927 None => Err(Error::NotCreated.into()),
928 }
929 }
930 #[instrument]
932 async fn playlist_remove(
933 self,
934 context: Context,
935 id: PlaylistId,
936 ) -> Result<(), SerializableLibraryError> {
937 let id = id.into();
938 info!("Removing playlist with id: {id}");
939
940 Playlist::delete(&self.db, id)
941 .await?
942 .ok_or(Error::NotFound)?;
943
944 Ok(())
945 }
946 #[instrument]
950 async fn playlist_clone(
951 self,
952 context: Context,
953 id: PlaylistId,
954 ) -> Result<PlaylistId, SerializableLibraryError> {
955 let id = id.into();
956 info!("Cloning playlist with id: {id}");
957
958 let new_playlist = Playlist::create_copy(&self.db, id)
959 .await?
960 .ok_or(Error::NotFound)?;
961
962 Ok(new_playlist.id.into())
963 }
964 #[instrument]
967 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
968 info!("Getting playlist ID: {name}");
969
970 Playlist::read_by_name(&self.db, name)
971 .await
972 .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
973 .ok()
974 .flatten()
975 .map(|playlist| playlist.id.into())
976 }
977 #[instrument]
980 async fn playlist_remove_songs(
981 self,
982 context: Context,
983 playlist: PlaylistId,
984 songs: Vec<SongId>,
985 ) -> Result<(), SerializableLibraryError> {
986 let playlist = playlist.into();
987 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
988 info!("Removing song from playlist: {playlist} ({songs:?})");
989
990 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
991 }
992 #[instrument]
995 async fn playlist_add(
996 self,
997 context: Context,
998 playlist: PlaylistId,
999 thing: schemas::RecordId,
1000 ) -> Result<(), SerializableLibraryError> {
1001 let playlist = playlist.into();
1002 info!("Adding thing to playlist: {playlist} ({thing})");
1003
1004 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1006
1007 Ok(Playlist::add_songs(
1008 &self.db,
1009 playlist,
1010 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1011 )
1012 .await?)
1013 }
1014 #[instrument]
1017 async fn playlist_add_list(
1018 self,
1019 context: Context,
1020 playlist: PlaylistId,
1021 list: Vec<schemas::RecordId>,
1022 ) -> Result<(), SerializableLibraryError> {
1023 let playlist = playlist.into();
1024 info!(
1025 "Adding list to playlist: {playlist} ({})",
1026 list.iter()
1027 .map(ToString::to_string)
1028 .collect::<Vec<_>>()
1029 .join(", ")
1030 );
1031
1032 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1034
1035 Ok(Playlist::add_songs(
1036 &self.db,
1037 playlist,
1038 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1039 )
1040 .await?)
1041 }
1042 #[instrument]
1044 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1045 let id = id.into();
1046 info!("Getting playlist by ID: {}", id);
1047
1048 Playlist::read(&self.db, id)
1049 .await
1050 .tap_err(|e| warn!("Error in playlist_get: {e}"))
1051 .ok()
1052 .flatten()
1053 }
1054 #[instrument]
1056 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1057 let id = id.into();
1058 info!("Getting songs in: {id}");
1059 Playlist::read_songs(&self.db, id)
1060 .await
1061 .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1062 .ok()
1063 .map(Into::into)
1064 }
1065 #[instrument]
1067 async fn playlist_rename(
1068 self,
1069 context: Context,
1070 id: PlaylistId,
1071 name: String,
1072 ) -> Result<Playlist, SerializableLibraryError> {
1073 let id = id.into();
1074 info!("Renaming playlist: {id} ({name})");
1075 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1076 .await?
1077 .ok_or(Error::NotFound.into())
1078 }
1079
1080 #[instrument]
1082 async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1083 info!("Listing collections");
1084 Collection::read_all(&self.db)
1085 .await
1086 .tap_err(|e| warn!("Error in collection_list: {e}"))
1087 .ok()
1088 .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1089 .unwrap_or_default()
1090 }
1091 #[instrument]
1093 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1094 info!("Getting collection by ID: {id:?}");
1095 Collection::read(&self.db, id.into())
1096 .await
1097 .tap_err(|e| warn!("Error in collection_get: {e}"))
1098 .ok()
1099 .flatten()
1100 }
1101 #[instrument]
1103 async fn collection_freeze(
1104 self,
1105 context: Context,
1106 id: CollectionId,
1107 name: String,
1108 ) -> Result<PlaylistId, SerializableLibraryError> {
1109 info!("Freezing collection: {id:?} ({name})");
1110 Ok(Collection::freeze(&self.db, id.into(), name)
1111 .await
1112 .map(|p| p.id.into())?)
1113 }
1114 #[instrument]
1116 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1117 let id = id.into();
1118 info!("Getting songs in: {id}");
1119 Collection::read_songs(&self.db, id)
1120 .await
1121 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1122 .ok()
1123 .map(Into::into)
1124 }
1125
1126 #[instrument]
1128 async fn radio_get_similar(
1129 self,
1130 context: Context,
1131 things: Vec<schemas::RecordId>,
1132 n: u32,
1133 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1134 #[cfg(not(feature = "analysis"))]
1135 {
1136 warn!("Analysis is not enabled");
1137 return Err(SerializableLibraryError::AnalysisNotEnabled);
1138 }
1139
1140 #[cfg(feature = "analysis")]
1141 {
1142 info!("Getting the {n} most similar songs to: {things:?}");
1143 Ok(services::radio::get_similar(&self.db, things, n)
1144 .await
1145 .map(Vec::into_boxed_slice)
1146 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1147 }
1148 }
1149 #[instrument]
1151 async fn radio_get_similar_ids(
1152 self,
1153 context: Context,
1154 things: Vec<schemas::RecordId>,
1155 n: u32,
1156 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1157 #[cfg(not(feature = "analysis"))]
1158 {
1159 warn!("Analysis is not enabled");
1160 return Err(SerializableLibraryError::AnalysisNotEnabled);
1161 }
1162
1163 #[cfg(feature = "analysis")]
1164 {
1165 info!("Getting the {n} most similar songs to: {things:?}");
1166 Ok(services::radio::get_similar(&self.db, things, n)
1167 .await
1168 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1169 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1170 }
1171 }
1172
1173 #[instrument]
1176 async fn dynamic_playlist_create(
1177 self,
1178 context: Context,
1179 name: String,
1180 query: Query,
1181 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1182 let id = DynamicPlaylist::generate_id();
1183 info!("Creating new DP: {id:?} ({name})");
1184
1185 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1186 .await
1187 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1188 {
1189 Some(dp) => Ok(dp.id.into()),
1190 None => Err(Error::NotCreated.into()),
1191 }
1192 }
1193 #[instrument]
1195 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1196 info!("Listing DPs");
1197 DynamicPlaylist::read_all(&self.db)
1198 .await
1199 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1200 .ok()
1201 .map(Into::into)
1202 .unwrap_or_default()
1203 }
1204 #[instrument]
1206 async fn dynamic_playlist_update(
1207 self,
1208 context: Context,
1209 id: DynamicPlaylistId,
1210 changes: DynamicPlaylistChangeSet,
1211 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1212 info!("Updating DP: {id:?}, {changes:?}");
1213 DynamicPlaylist::update(&self.db, id.into(), changes)
1214 .await
1215 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1216 .ok_or(Error::NotFound.into())
1217 }
1218 #[instrument]
1220 async fn dynamic_playlist_remove(
1221 self,
1222 context: Context,
1223 id: DynamicPlaylistId,
1224 ) -> Result<(), SerializableLibraryError> {
1225 info!("Removing DP with id: {id:?}");
1226 DynamicPlaylist::delete(&self.db, id.into())
1227 .await?
1228 .ok_or(Error::NotFound)?;
1229 Ok(())
1230 }
1231 #[instrument]
1233 async fn dynamic_playlist_get(
1234 self,
1235 context: Context,
1236 id: DynamicPlaylistId,
1237 ) -> Option<DynamicPlaylist> {
1238 info!("Getting DP by ID: {id:?}");
1239 DynamicPlaylist::read(&self.db, id.into())
1240 .await
1241 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1242 .ok()
1243 .flatten()
1244 }
1245 #[instrument]
1247 async fn dynamic_playlist_get_songs(
1248 self,
1249 context: Context,
1250 id: DynamicPlaylistId,
1251 ) -> Option<Box<[Song]>> {
1252 info!("Getting songs in DP: {id:?}");
1253 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1254 .await
1255 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1256 .ok()
1257 .flatten()
1258 .map(Into::into)
1259 }
1260}