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