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(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 async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
626 info!("Searching for: {query}");
627 let songs = Song::search(&self.db, &query, i64::from(limit))
633 .await
634 .tap_err(|e| warn!("Error in search: {e}"))
635 .unwrap_or_default()
636 .into();
637
638 let albums = Album::search(&self.db, &query, i64::from(limit))
639 .await
640 .tap_err(|e| warn!("Error in search: {e}"))
641 .unwrap_or_default()
642 .into();
643
644 let artists = Artist::search(&self.db, &query, i64::from(limit))
645 .await
646 .tap_err(|e| warn!("Error in search: {e}"))
647 .unwrap_or_default()
648 .into();
649 SearchResult {
650 songs,
651 albums,
652 artists,
653 }
654 }
655 #[instrument]
657 async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
658 info!("Searching for artist: {query}");
659 Artist::search(&self.db, &query, i64::from(limit))
660 .await
661 .tap_err(|e| {
662 warn!("Error in search_artist: {e}");
663 })
664 .unwrap_or_default()
665 .into()
666 }
667 #[instrument]
669 async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
670 info!("Searching for album: {query}");
671 Album::search(&self.db, &query, i64::from(limit))
672 .await
673 .tap_err(|e| {
674 warn!("Error in search_album: {e}");
675 })
676 .unwrap_or_default()
677 .into()
678 }
679 #[instrument]
681 async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
682 info!("Searching for song: {query}");
683 Song::search(&self.db, &query, i64::from(limit))
684 .await
685 .tap_err(|e| {
686 warn!("Error in search_song: {e}");
687 })
688 .unwrap_or_default()
689 .into()
690 }
691
692 #[instrument]
694 async fn playback_toggle(self, context: Context) {
695 info!("Toggling playback");
696 self.audio_kernel.send(AudioCommand::TogglePlayback);
697 }
698 #[instrument]
700 async fn playback_play(self, context: Context) {
701 info!("Starting playback");
702 self.audio_kernel.send(AudioCommand::Play);
703 }
704 #[instrument]
706 async fn playback_pause(self, context: Context) {
707 info!("Pausing playback");
708 self.audio_kernel.send(AudioCommand::Pause);
709 }
710 #[instrument]
712 async fn playback_stop(self, context: Context) {
713 info!("Stopping playback");
714 self.audio_kernel.send(AudioCommand::Stop);
715 }
716 #[instrument]
718 async fn playback_restart(self, context: Context) {
719 info!("Restarting current song");
720 self.audio_kernel.send(AudioCommand::RestartSong);
721 }
722 #[instrument]
724 async fn playback_skip_forward(self, context: Context, amount: usize) {
725 info!("Skipping forward by {amount} songs");
726 self.audio_kernel
727 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
728 }
729 #[instrument]
731 async fn playback_skip_backward(self, context: Context, amount: usize) {
732 info!("Going back by {amount} songs");
733 self.audio_kernel
734 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
735 }
736 #[instrument]
739 async fn playback_clear_player(self, context: Context) {
740 info!("Stopping playback");
741 self.audio_kernel.send(AudioCommand::ClearPlayer);
742 }
743 #[instrument]
745 async fn playback_clear(self, context: Context) {
746 info!("Clearing queue and stopping playback");
747 self.audio_kernel
748 .send(AudioCommand::Queue(QueueCommand::Clear));
749 }
750 #[instrument]
752 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
753 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
754 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
755 }
756 #[instrument]
758 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
759 info!("Setting repeat mode to: {}", mode);
760 self.audio_kernel
761 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
762 }
763 #[instrument]
765 async fn playback_shuffle(self, context: Context) {
766 info!("Shuffling queue");
767 self.audio_kernel
768 .send(AudioCommand::Queue(QueueCommand::Shuffle));
769 }
770 #[instrument]
773 async fn playback_volume(self, context: Context, volume: f32) {
774 info!("Setting volume to: {volume}",);
775 self.audio_kernel
776 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
777 }
778 #[instrument]
780 async fn playback_volume_up(self, context: Context, amount: f32) {
781 info!("Increasing volume by: {amount}",);
782 self.audio_kernel
783 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
784 }
785 #[instrument]
787 async fn playback_volume_down(self, context: Context, amount: f32) {
788 info!("Decreasing volume by: {amount}",);
789 self.audio_kernel
790 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
791 }
792 #[instrument]
794 async fn playback_volume_toggle_mute(self, context: Context) {
795 info!("Toggling volume mute");
796 self.audio_kernel
797 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
798 }
799 #[instrument]
801 async fn playback_mute(self, context: Context) {
802 info!("Muting volume");
803 self.audio_kernel
804 .send(AudioCommand::Volume(VolumeCommand::Mute));
805 }
806 #[instrument]
808 async fn playback_unmute(self, context: Context) {
809 info!("Unmuting volume");
810 self.audio_kernel
811 .send(AudioCommand::Volume(VolumeCommand::Unmute));
812 }
813
814 #[instrument]
817 async fn queue_add(
818 self,
819 context: Context,
820 thing: schemas::Thing,
821 ) -> Result<(), SerializableLibraryError> {
822 info!("Adding thing to queue: {thing}");
823
824 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
825
826 if songs.is_empty() {
827 return Err(Error::NotFound.into());
828 }
829
830 self.audio_kernel
831 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
832 songs,
833 ))));
834
835 Ok(())
836 }
837 #[instrument]
840 async fn queue_add_list(
841 self,
842 context: Context,
843 list: Vec<schemas::Thing>,
844 ) -> Result<(), SerializableLibraryError> {
845 info!(
846 "Adding list to queue: ({})",
847 list.iter()
848 .map(ToString::to_string)
849 .collect::<Vec<_>>()
850 .join(", ")
851 );
852
853 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
855
856 self.audio_kernel
857 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
858 songs,
859 ))));
860
861 Ok(())
862 }
863 #[instrument]
866 async fn queue_set_index(self, context: Context, index: usize) {
867 info!("Setting queue index to: {index}");
868
869 self.audio_kernel
870 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
871 }
872 #[instrument]
875 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
876 info!("Removing queue range: {range:?}");
877
878 self.audio_kernel
879 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
880 }
881
882 #[instrument]
884 async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
885 info!("Listing playlists");
886 Playlist::read_all(&self.db)
887 .await
888 .tap_err(|e| warn!("Error in playlist_list: {e}"))
889 .ok()
890 .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
891 .unwrap_or_default()
892 }
893 #[instrument]
896 async fn playlist_get_or_create(
897 self,
898 context: Context,
899 name: String,
900 ) -> Result<PlaylistId, SerializableLibraryError> {
901 info!("Creating new playlist: {name}");
902
903 match Playlist::read_by_name(&self.db, name.clone()).await {
905 Ok(Some(playlist)) => return Ok(playlist.id.into()),
906 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
907 _ => {}
908 }
909 match Playlist::create(
911 &self.db,
912 Playlist {
913 id: Playlist::generate_id(),
914 name,
915 runtime: Duration::from_secs(0),
916 song_count: 0,
917 },
918 )
919 .await
920 .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
921 {
922 Some(playlist) => Ok(playlist.id.into()),
923 None => Err(Error::NotCreated.into()),
924 }
925 }
926 #[instrument]
928 async fn playlist_remove(
929 self,
930 context: Context,
931 id: PlaylistId,
932 ) -> Result<(), SerializableLibraryError> {
933 let id = id.into();
934 info!("Removing playlist with id: {id}");
935
936 Playlist::delete(&self.db, id)
937 .await?
938 .ok_or(Error::NotFound)?;
939
940 Ok(())
941 }
942 #[instrument]
946 async fn playlist_clone(
947 self,
948 context: Context,
949 id: PlaylistId,
950 ) -> Result<PlaylistId, SerializableLibraryError> {
951 let id = id.into();
952 info!("Cloning playlist with id: {id}");
953
954 let new_playlist = Playlist::create_copy(&self.db, id)
955 .await?
956 .ok_or(Error::NotFound)?;
957
958 Ok(new_playlist.id.into())
959 }
960 #[instrument]
963 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
964 info!("Getting playlist ID: {name}");
965
966 Playlist::read_by_name(&self.db, name)
967 .await
968 .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
969 .ok()
970 .flatten()
971 .map(|playlist| playlist.id.into())
972 }
973 #[instrument]
976 async fn playlist_remove_songs(
977 self,
978 context: Context,
979 playlist: PlaylistId,
980 songs: Vec<SongId>,
981 ) -> Result<(), SerializableLibraryError> {
982 let playlist = playlist.into();
983 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
984 info!("Removing song from playlist: {playlist} ({songs:?})");
985
986 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
987 }
988 #[instrument]
991 async fn playlist_add(
992 self,
993 context: Context,
994 playlist: PlaylistId,
995 thing: schemas::Thing,
996 ) -> Result<(), SerializableLibraryError> {
997 let playlist = playlist.into();
998 info!("Adding thing to playlist: {playlist} ({thing})");
999
1000 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1002
1003 Ok(Playlist::add_songs(
1004 &self.db,
1005 playlist,
1006 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1007 )
1008 .await?)
1009 }
1010 #[instrument]
1013 async fn playlist_add_list(
1014 self,
1015 context: Context,
1016 playlist: PlaylistId,
1017 list: Vec<schemas::Thing>,
1018 ) -> Result<(), SerializableLibraryError> {
1019 let playlist = playlist.into();
1020 info!(
1021 "Adding list to playlist: {playlist} ({})",
1022 list.iter()
1023 .map(ToString::to_string)
1024 .collect::<Vec<_>>()
1025 .join(", ")
1026 );
1027
1028 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1030
1031 Ok(Playlist::add_songs(
1032 &self.db,
1033 playlist,
1034 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1035 )
1036 .await?)
1037 }
1038 #[instrument]
1040 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1041 let id = id.into();
1042 info!("Getting playlist by ID: {}", id);
1043
1044 Playlist::read(&self.db, id)
1045 .await
1046 .tap_err(|e| warn!("Error in playlist_get: {e}"))
1047 .ok()
1048 .flatten()
1049 }
1050 #[instrument]
1052 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1053 let id = id.into();
1054 info!("Getting songs in: {id}");
1055 Playlist::read_songs(&self.db, id)
1056 .await
1057 .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1058 .ok()
1059 .map(Into::into)
1060 }
1061 #[instrument]
1063 async fn playlist_rename(
1064 self,
1065 context: Context,
1066 id: PlaylistId,
1067 name: String,
1068 ) -> Result<Playlist, SerializableLibraryError> {
1069 let id = id.into();
1070 info!("Renaming playlist: {id} ({name})");
1071 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1072 .await?
1073 .ok_or(Error::NotFound.into())
1074 }
1075
1076 #[instrument]
1078 async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1079 info!("Listing collections");
1080 Collection::read_all(&self.db)
1081 .await
1082 .tap_err(|e| warn!("Error in collection_list: {e}"))
1083 .ok()
1084 .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1085 .unwrap_or_default()
1086 }
1087 #[instrument]
1089 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1090 info!("Getting collection by ID: {id:?}");
1091 Collection::read(&self.db, id.into())
1092 .await
1093 .tap_err(|e| warn!("Error in collection_get: {e}"))
1094 .ok()
1095 .flatten()
1096 }
1097 #[instrument]
1099 async fn collection_freeze(
1100 self,
1101 context: Context,
1102 id: CollectionId,
1103 name: String,
1104 ) -> Result<PlaylistId, SerializableLibraryError> {
1105 info!("Freezing collection: {id:?} ({name})");
1106 Ok(Collection::freeze(&self.db, id.into(), name)
1107 .await
1108 .map(|p| p.id.into())?)
1109 }
1110 #[instrument]
1112 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1113 let id = id.into();
1114 info!("Getting songs in: {id}");
1115 Collection::read_songs(&self.db, id)
1116 .await
1117 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1118 .ok()
1119 .map(Into::into)
1120 }
1121
1122 #[instrument]
1124 async fn radio_get_similar(
1125 self,
1126 context: Context,
1127 things: Vec<schemas::Thing>,
1128 n: u32,
1129 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1130 #[cfg(not(feature = "analysis"))]
1131 {
1132 warn!("Analysis is not enabled");
1133 return Err(SerializableLibraryError::AnalysisNotEnabled);
1134 }
1135
1136 #[cfg(feature = "analysis")]
1137 {
1138 info!("Getting the {n} most similar songs to: {things:?}");
1139 Ok(services::radio::get_similar(&self.db, things, n)
1140 .await
1141 .map(Vec::into_boxed_slice)
1142 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1143 }
1144 }
1145 #[instrument]
1147 async fn radio_get_similar_ids(
1148 self,
1149 context: Context,
1150 things: Vec<schemas::Thing>,
1151 n: u32,
1152 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1153 #[cfg(not(feature = "analysis"))]
1154 {
1155 warn!("Analysis is not enabled");
1156 return Err(SerializableLibraryError::AnalysisNotEnabled);
1157 }
1158
1159 #[cfg(feature = "analysis")]
1160 {
1161 info!("Getting the {n} most similar songs to: {things:?}");
1162 Ok(services::radio::get_similar(&self.db, things, n)
1163 .await
1164 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1165 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1166 }
1167 }
1168
1169 #[instrument]
1172 async fn dynamic_playlist_create(
1173 self,
1174 context: Context,
1175 name: String,
1176 query: Query,
1177 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1178 let id = DynamicPlaylist::generate_id();
1179 info!("Creating new DP: {id:?} ({name})");
1180
1181 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1182 .await
1183 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1184 {
1185 Some(dp) => Ok(dp.id.into()),
1186 None => Err(Error::NotCreated.into()),
1187 }
1188 }
1189 #[instrument]
1191 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1192 info!("Listing DPs");
1193 DynamicPlaylist::read_all(&self.db)
1194 .await
1195 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1196 .ok()
1197 .map(Into::into)
1198 .unwrap_or_default()
1199 }
1200 #[instrument]
1202 async fn dynamic_playlist_update(
1203 self,
1204 context: Context,
1205 id: DynamicPlaylistId,
1206 changes: DynamicPlaylistChangeSet,
1207 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1208 info!("Updating DP: {id:?}, {changes:?}");
1209 DynamicPlaylist::update(&self.db, id.into(), changes)
1210 .await
1211 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1212 .ok_or(Error::NotFound.into())
1213 }
1214 #[instrument]
1216 async fn dynamic_playlist_remove(
1217 self,
1218 context: Context,
1219 id: DynamicPlaylistId,
1220 ) -> Result<(), SerializableLibraryError> {
1221 info!("Removing DP with id: {id:?}");
1222 DynamicPlaylist::delete(&self.db, id.into())
1223 .await?
1224 .ok_or(Error::NotFound)?;
1225 Ok(())
1226 }
1227 #[instrument]
1229 async fn dynamic_playlist_get(
1230 self,
1231 context: Context,
1232 id: DynamicPlaylistId,
1233 ) -> Option<DynamicPlaylist> {
1234 info!("Getting DP by ID: {id:?}");
1235 DynamicPlaylist::read(&self.db, id.into())
1236 .await
1237 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1238 .ok()
1239 .flatten()
1240 }
1241 #[instrument]
1243 async fn dynamic_playlist_get_songs(
1244 self,
1245 context: Context,
1246 id: DynamicPlaylistId,
1247 ) -> Option<Box<[Song]>> {
1248 info!("Getting songs in DP: {id:?}");
1249 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1250 .await
1251 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1252 .ok()
1253 .flatten()
1254 .map(Into::into)
1255 }
1256}