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