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::{
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.genre_separator.as_deref(),
131 self.settings.daemon.conflict_resolution,
132 )
133 .await
134 {
135 Ok(()) => info!("Library rescan complete"),
136 Err(e) => error!("Error in library_rescan: {e}"),
137 }
138
139 let result = self.publish(Event::LibraryRescanFinished).await;
140 if let Err(e) = result {
141 error!("Error notifying clients that library_rescan_finished: {e}");
142 }
143 }
144 .instrument(span),
145 );
146 })?;
147
148 Ok(())
149 }
150 #[instrument]
152 async fn library_rescan_in_progress(self, context: Context) -> bool {
153 self.library_rescan_lock.try_lock().is_err()
154 }
155 #[instrument]
157 async fn library_analyze(
158 self,
159 context: Context,
160 overwrite: bool,
161 ) -> Result<(), SerializableLibraryError> {
162 #[cfg(not(feature = "analysis"))]
163 {
164 warn!("Analysis is not enabled");
165 return Err(SerializableLibraryError::AnalysisNotEnabled);
166 }
167
168 #[cfg(feature = "analysis")]
169 {
170 info!("Analyzing library");
171
172 if self.library_analyze_lock.try_lock().is_err() {
173 warn!("Library analysis already in progress");
174 return Err(SerializableLibraryError::AnalysisInProgress);
175 }
176 let span = tracing::Span::current();
177
178 std::thread::Builder::new()
179 .name(String::from("Library Analysis"))
180 .spawn(move || {
181 futures::executor::block_on(
182 async {
183 let _guard = self.library_analyze_lock.lock().await;
184 match services::library::analyze(&self.db, overwrite).await {
185 Ok(()) => info!("Library analysis complete"),
186 Err(e) => error!("Error in library_analyze: {e}"),
187 }
188
189 let result = &self.publish(Event::LibraryAnalysisFinished).await;
190 if let Err(e) = result {
191 error!(
192 "Error notifying clients that library_analysis_finished: {e}"
193 );
194 }
195 }
196 .instrument(span),
197 );
198 })?;
199
200 Ok(())
201 }
202 }
203 #[instrument]
205 async fn library_analyze_in_progress(self, context: Context) -> bool {
206 self.library_analyze_lock.try_lock().is_err()
207 }
208 #[instrument]
210 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
211 #[cfg(not(feature = "analysis"))]
212 {
213 warn!("Analysis is not enabled");
214 return Err(SerializableLibraryError::AnalysisNotEnabled);
215 }
216
217 #[cfg(feature = "analysis")]
218 {
219 info!("Reclustering collections");
220
221 if self.collection_recluster_lock.try_lock().is_err() {
222 warn!("Collection reclustering already in progress");
223 return Err(SerializableLibraryError::ReclusterInProgress);
224 }
225
226 let span = tracing::Span::current();
227
228 std::thread::Builder::new()
229 .name(String::from("Collection Recluster"))
230 .spawn(move || {
231 futures::executor::block_on(
232 async {
233 let _guard = self.collection_recluster_lock.lock().await;
234 match services::library::recluster(
235 &self.db,
236 &self.settings.reclustering,
237 )
238 .await
239 {
240 Ok(()) => info!("Collection reclustering complete"),
241 Err(e) => error!("Error in collection_recluster: {e}"),
242 }
243
244 let result = &self.publish(Event::LibraryReclusterFinished).await;
245 if let Err(e) = result {
246 error!(
247 "Error notifying clients that library_recluster_finished: {e}"
248 );
249 }
250 }
251 .instrument(span),
252 );
253 })?;
254
255 Ok(())
256 }
257 }
258 #[instrument]
260 async fn library_recluster_in_progress(self, context: Context) -> bool {
261 self.collection_recluster_lock.try_lock().is_err()
262 }
263 #[instrument]
265 async fn library_brief(
266 self,
267 context: Context,
268 ) -> Result<LibraryBrief, SerializableLibraryError> {
269 info!("Creating library brief");
270 Ok(services::library::brief(&self.db)
271 .await
272 .tap_err(|e| warn!("Error in library_brief: {e}"))?)
273 }
274 #[instrument]
276 async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
277 info!("Creating library full");
278 Ok(services::library::full(&self.db)
279 .await
280 .tap_err(|e| warn!("Error in library_full: {e}"))?)
281 }
282 #[instrument]
284 async fn library_artists_brief(
285 self,
286 context: Context,
287 ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
288 info!("Creating library artists brief");
289 Ok(Artist::read_all(&self.db)
290 .await
291 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
292 .iter()
293 .map(std::convert::Into::into)
294 .collect())
295 }
296 #[instrument]
298 async fn library_artists_full(
299 self,
300 context: Context,
301 ) -> Result<Box<[Artist]>, SerializableLibraryError> {
302 info!("Creating library artists full");
303 Ok(Artist::read_all(&self.db)
304 .await
305 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
306 .into_boxed_slice())
307 }
308 #[instrument]
310 async fn library_albums_brief(
311 self,
312 context: Context,
313 ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
314 info!("Creating library albums brief");
315 Ok(Album::read_all(&self.db)
316 .await
317 .tap_err(|e| warn!("Error in library_albums_brief: {e}"))?
318 .iter()
319 .map(std::convert::Into::into)
320 .collect())
321 }
322 #[instrument]
324 async fn library_albums_full(
325 self,
326 context: Context,
327 ) -> Result<Box<[Album]>, SerializableLibraryError> {
328 info!("Creating library albums full");
329 Ok(Album::read_all(&self.db)
330 .await
331 .map(std::vec::Vec::into_boxed_slice)
332 .tap_err(|e| warn!("Error in library_albums_full: {e}"))?)
333 }
334 #[instrument]
336 async fn library_songs_brief(
337 self,
338 context: Context,
339 ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
340 info!("Creating library songs brief");
341 Ok(Song::read_all(&self.db)
342 .await
343 .tap_err(|e| warn!("Error in library_songs_brief: {e}"))?
344 .iter()
345 .map(std::convert::Into::into)
346 .collect())
347 }
348 #[instrument]
350 async fn library_songs_full(
351 self,
352 context: Context,
353 ) -> Result<Box<[Song]>, SerializableLibraryError> {
354 info!("Creating library songs full");
355 Ok(Song::read_all(&self.db)
356 .await
357 .map(std::vec::Vec::into_boxed_slice)
358 .tap_err(|e| warn!("Error in library_songs_full: {e}"))?)
359 }
360 #[instrument]
362 async fn library_health(
363 self,
364 context: Context,
365 ) -> Result<LibraryHealth, SerializableLibraryError> {
366 info!("Creating library health");
367 Ok(services::library::health(&self.db)
368 .await
369 .tap_err(|e| warn!("Error in library_health: {e}"))?)
370 }
371 #[instrument]
373 async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
374 let id = id.into();
375 info!("Getting song by ID: {id}");
376 Song::read(&self.db, id)
377 .await
378 .tap_err(|e| warn!("Error in library_song_get: {e}"))
379 .ok()
380 .flatten()
381 }
382 #[instrument]
384 async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
385 info!("Getting song by path: {}", path.display());
386 Song::read_by_path(&self.db, path)
387 .await
388 .tap_err(|e| warn!("Error in library_song_get_by_path: {e}"))
389 .ok()
390 .flatten()
391 }
392 #[instrument]
394 async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
395 let id = id.into();
396 info!("Getting artist of: {id}");
397 Song::read_artist(&self.db, id)
398 .await
399 .tap_err(|e| warn!("Error in library_song_get_artist: {e}"))
400 .ok()
401 .into()
402 }
403 #[instrument]
405 async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
406 let id = id.into();
407 info!("Getting album of: {id}");
408 Song::read_album(&self.db, id)
409 .await
410 .tap_err(|e| warn!("Error in library_song_get_album: {e}"))
411 .ok()
412 .flatten()
413 }
414 #[instrument]
416 async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
417 let id = id.into();
418 info!("Getting playlists of: {id}");
419 Song::read_playlists(&self.db, id)
420 .await
421 .tap_err(|e| warn!("Error in library_song_get_playlists: {e}"))
422 .ok()
423 .unwrap_or_default()
424 .into()
425 }
426 #[instrument]
428 async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
429 let id = id.into();
430 info!("Getting collections of: {id}");
431 Song::read_collections(&self.db, id)
432 .await
433 .tap_err(|e| warn!("Error in library_song_get_collections: {e}"))
434 .ok()
435 .unwrap_or_default()
436 .into()
437 }
438
439 #[instrument]
441 async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
442 let id = id.into();
443 info!("Getting album by ID: {id}");
444 Album::read(&self.db, id)
445 .await
446 .tap_err(|e| warn!("Error in library_album_get: {e}"))
447 .ok()
448 .flatten()
449 }
450 #[instrument]
452 async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
453 let id = id.into();
454 info!("Getting artists of: {id}");
455 Album::read_artist(&self.db, id)
456 .await
457 .tap_err(|e| warn!("Error in library_album_get_artist: {e}"))
458 .ok()
459 .into()
460 }
461 #[instrument]
463 async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
464 let id = id.into();
465 info!("Getting songs of: {id}");
466 Album::read_songs(&self.db, id)
467 .await
468 .tap_err(|e| warn!("Error in library_album_get_songs: {e}"))
469 .ok()
470 .map(Into::into)
471 }
472 #[instrument]
474 async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
475 let id = id.into();
476 info!("Getting artist by ID: {id}");
477 Artist::read(&self.db, id)
478 .await
479 .tap_err(|e| warn!("Error in library_artist_get: {e}"))
480 .ok()
481 .flatten()
482 }
483 #[instrument]
485 async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
486 let id = id.into();
487 info!("Getting songs of: {id}");
488 Artist::read_songs(&self.db, id)
489 .await
490 .tap_err(|e| warn!("Error in library_artist_get_songs: {e}"))
491 .ok()
492 .map(Into::into)
493 }
494 #[instrument]
496 async fn library_artist_get_albums(
497 self,
498 context: Context,
499 id: ArtistId,
500 ) -> Option<Box<[Album]>> {
501 let id = id.into();
502 info!("Getting albums of: {id}");
503 Artist::read_albums(&self.db, id)
504 .await
505 .tap_err(|e| warn!("Error in library_artist_get_albums: {e}"))
506 .ok()
507 .map(Into::into)
508 }
509
510 #[instrument]
512 async fn daemon_shutdown(self, context: Context) {
513 let terminator = self.terminator.clone();
514 std::thread::Builder::new()
515 .name(String::from("Daemon Shutdown"))
516 .spawn(move || {
517 std::thread::sleep(std::time::Duration::from_secs(1));
518 let terminate_result = terminator
519 .blocking_lock()
520 .terminate(termination::Interrupted::UserInt);
521 if let Err(e) = terminate_result {
522 error!("Error terminating daemon, panicking instead: {e}");
523 panic!("Error terminating daemon: {e}");
524 }
525 })
526 .unwrap();
527 info!("Shutting down daemon in 1 second");
528 }
529
530 #[instrument]
532 async fn state_audio(self, context: Context) -> Option<StateAudio> {
533 debug!("Getting state of audio player");
534 let (tx, rx) = tokio::sync::oneshot::channel();
535
536 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
537
538 rx.await
539 .tap_err(|e| warn!("Error in state_audio: {e}"))
540 .ok()
541 }
542
543 #[instrument]
545 async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
546 info!("Getting current artist");
547 let (tx, rx) = tokio::sync::oneshot::channel();
548
549 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
550
551 if let Some(song) = rx
552 .await
553 .tap_err(|e| warn!("Error in current_artist: {e}"))
554 .ok()
555 .and_then(|state| state.current_song)
556 {
557 Song::read_artist(&self.db, song.id)
558 .await
559 .tap_err(|e| warn!("Error in current_album: {e}"))
560 .ok()
561 .into()
562 } else {
563 OneOrMany::None
564 }
565 }
566 #[instrument]
568 async fn current_album(self, context: Context) -> Option<Album> {
569 info!("Getting current album");
570 let (tx, rx) = tokio::sync::oneshot::channel();
571
572 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
573
574 if let Some(song) = rx
575 .await
576 .tap_err(|e| warn!("Error in current_album: {e}"))
577 .ok()
578 .and_then(|state| state.current_song)
579 {
580 Song::read_album(&self.db, song.id)
581 .await
582 .tap_err(|e| warn!("Error in current_album: {e}"))
583 .ok()
584 .flatten()
585 } else {
586 None
587 }
588 }
589 #[instrument]
591 async fn current_song(self, context: Context) -> Option<Song> {
592 info!("Getting current song");
593 let (tx, rx) = tokio::sync::oneshot::channel();
594
595 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
596
597 rx.await
598 .tap_err(|e| warn!("Error in current_song: {e}"))
599 .ok()
600 .and_then(|state| state.current_song)
601 }
602
603 #[instrument]
605 async fn rand_artist(self, context: Context) -> Option<Artist> {
606 info!("Getting random artist");
607 Artist::read_all(&self.db)
608 .await
609 .tap_err(|e| warn!("Error in rand_artist: {e}"))
610 .ok()
611 .and_then(|artists| artists.choose(&mut rand::thread_rng()).cloned())
612 }
613 #[instrument]
615 async fn rand_album(self, context: Context) -> Option<Album> {
616 info!("Getting random album");
617 Album::read_all(&self.db)
618 .await
619 .tap_err(|e| warn!("Error in rand_album: {e}"))
620 .ok()
621 .and_then(|albums| albums.choose(&mut rand::thread_rng()).cloned())
622 }
623 #[instrument]
625 async fn rand_song(self, context: Context) -> Option<Song> {
626 info!("Getting random song");
627 Song::read_all(&self.db)
628 .await
629 .tap_err(|e| warn!("Error in rand_song: {e}"))
630 .ok()
631 .and_then(|songs| songs.choose(&mut rand::thread_rng()).cloned())
632 }
633
634 #[instrument]
636 async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
637 info!("Searching for: {query}");
638 let songs = Song::search(&self.db, &query, i64::from(limit))
644 .await
645 .tap_err(|e| warn!("Error in search: {e}"))
646 .unwrap_or_default()
647 .into();
648
649 let albums = Album::search(&self.db, &query, i64::from(limit))
650 .await
651 .tap_err(|e| warn!("Error in search: {e}"))
652 .unwrap_or_default()
653 .into();
654
655 let artists = Artist::search(&self.db, &query, i64::from(limit))
656 .await
657 .tap_err(|e| warn!("Error in search: {e}"))
658 .unwrap_or_default()
659 .into();
660 SearchResult {
661 songs,
662 albums,
663 artists,
664 }
665 }
666 #[instrument]
668 async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
669 info!("Searching for artist: {query}");
670 Artist::search(&self.db, &query, i64::from(limit))
671 .await
672 .tap_err(|e| {
673 warn!("Error in search_artist: {e}");
674 })
675 .unwrap_or_default()
676 .into()
677 }
678 #[instrument]
680 async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
681 info!("Searching for album: {query}");
682 Album::search(&self.db, &query, i64::from(limit))
683 .await
684 .tap_err(|e| {
685 warn!("Error in search_album: {e}");
686 })
687 .unwrap_or_default()
688 .into()
689 }
690 #[instrument]
692 async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
693 info!("Searching for song: {query}");
694 Song::search(&self.db, &query, i64::from(limit))
695 .await
696 .tap_err(|e| {
697 warn!("Error in search_song: {e}");
698 })
699 .unwrap_or_default()
700 .into()
701 }
702
703 #[instrument]
705 async fn playback_toggle(self, context: Context) {
706 info!("Toggling playback");
707 self.audio_kernel.send(AudioCommand::TogglePlayback);
708 }
709 #[instrument]
711 async fn playback_play(self, context: Context) {
712 info!("Starting playback");
713 self.audio_kernel.send(AudioCommand::Play);
714 }
715 #[instrument]
717 async fn playback_pause(self, context: Context) {
718 info!("Pausing playback");
719 self.audio_kernel.send(AudioCommand::Pause);
720 }
721 #[instrument]
723 async fn playback_stop(self, context: Context) {
724 info!("Stopping playback");
725 self.audio_kernel.send(AudioCommand::Stop);
726 }
727 #[instrument]
729 async fn playback_restart(self, context: Context) {
730 info!("Restarting current song");
731 self.audio_kernel.send(AudioCommand::RestartSong);
732 }
733 #[instrument]
735 async fn playback_skip_forward(self, context: Context, amount: usize) {
736 info!("Skipping forward by {amount} songs");
737 self.audio_kernel
738 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
739 }
740 #[instrument]
742 async fn playback_skip_backward(self, context: Context, amount: usize) {
743 info!("Going back by {amount} songs");
744 self.audio_kernel
745 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
746 }
747 #[instrument]
750 async fn playback_clear_player(self, context: Context) {
751 info!("Stopping playback");
752 self.audio_kernel.send(AudioCommand::ClearPlayer);
753 }
754 #[instrument]
756 async fn playback_clear(self, context: Context) {
757 info!("Clearing queue and stopping playback");
758 self.audio_kernel
759 .send(AudioCommand::Queue(QueueCommand::Clear));
760 }
761 #[instrument]
763 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
764 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
765 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
766 }
767 #[instrument]
769 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
770 info!("Setting repeat mode to: {mode}");
771 self.audio_kernel
772 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
773 }
774 #[instrument]
776 async fn playback_shuffle(self, context: Context) {
777 info!("Shuffling queue");
778 self.audio_kernel
779 .send(AudioCommand::Queue(QueueCommand::Shuffle));
780 }
781 #[instrument]
784 async fn playback_volume(self, context: Context, volume: f32) {
785 info!("Setting volume to: {volume}",);
786 self.audio_kernel
787 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
788 }
789 #[instrument]
791 async fn playback_volume_up(self, context: Context, amount: f32) {
792 info!("Increasing volume by: {amount}",);
793 self.audio_kernel
794 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
795 }
796 #[instrument]
798 async fn playback_volume_down(self, context: Context, amount: f32) {
799 info!("Decreasing volume by: {amount}",);
800 self.audio_kernel
801 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
802 }
803 #[instrument]
805 async fn playback_volume_toggle_mute(self, context: Context) {
806 info!("Toggling volume mute");
807 self.audio_kernel
808 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
809 }
810 #[instrument]
812 async fn playback_mute(self, context: Context) {
813 info!("Muting volume");
814 self.audio_kernel
815 .send(AudioCommand::Volume(VolumeCommand::Mute));
816 }
817 #[instrument]
819 async fn playback_unmute(self, context: Context) {
820 info!("Unmuting volume");
821 self.audio_kernel
822 .send(AudioCommand::Volume(VolumeCommand::Unmute));
823 }
824
825 #[instrument]
828 async fn queue_add(
829 self,
830 context: Context,
831 thing: schemas::RecordId,
832 ) -> Result<(), SerializableLibraryError> {
833 info!("Adding thing to queue: {thing}");
834
835 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
836
837 if songs.is_empty() {
838 return Err(Error::NotFound.into());
839 }
840
841 self.audio_kernel
842 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
843 songs,
844 ))));
845
846 Ok(())
847 }
848 #[instrument]
851 async fn queue_add_list(
852 self,
853 context: Context,
854 list: Vec<schemas::RecordId>,
855 ) -> Result<(), SerializableLibraryError> {
856 info!(
857 "Adding list to queue: ({})",
858 list.iter()
859 .map(ToString::to_string)
860 .collect::<Vec<_>>()
861 .join(", ")
862 );
863
864 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
866
867 self.audio_kernel
868 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
869 songs,
870 ))));
871
872 Ok(())
873 }
874 #[instrument]
877 async fn queue_set_index(self, context: Context, index: usize) {
878 info!("Setting queue index to: {index}");
879
880 self.audio_kernel
881 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
882 }
883 #[instrument]
886 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
887 info!("Removing queue range: {range:?}");
888
889 self.audio_kernel
890 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
891 }
892
893 #[instrument]
895 async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
896 info!("Listing playlists");
897 Playlist::read_all(&self.db)
898 .await
899 .tap_err(|e| warn!("Error in playlist_list: {e}"))
900 .ok()
901 .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
902 .unwrap_or_default()
903 }
904 #[instrument]
907 async fn playlist_get_or_create(
908 self,
909 context: Context,
910 name: String,
911 ) -> Result<PlaylistId, SerializableLibraryError> {
912 info!("Creating new playlist: {name}");
913
914 match Playlist::read_by_name(&self.db, name.clone()).await {
916 Ok(Some(playlist)) => return Ok(playlist.id.into()),
917 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
918 _ => {}
919 }
920 match Playlist::create(
922 &self.db,
923 Playlist {
924 id: Playlist::generate_id(),
925 name,
926 runtime: Duration::from_secs(0),
927 song_count: 0,
928 },
929 )
930 .await
931 .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
932 {
933 Some(playlist) => Ok(playlist.id.into()),
934 None => Err(Error::NotCreated.into()),
935 }
936 }
937 #[instrument]
939 async fn playlist_remove(
940 self,
941 context: Context,
942 id: PlaylistId,
943 ) -> Result<(), SerializableLibraryError> {
944 let id = id.into();
945 info!("Removing playlist with id: {id}");
946
947 Playlist::delete(&self.db, id)
948 .await?
949 .ok_or(Error::NotFound)?;
950
951 Ok(())
952 }
953 #[instrument]
957 async fn playlist_clone(
958 self,
959 context: Context,
960 id: PlaylistId,
961 ) -> Result<PlaylistId, SerializableLibraryError> {
962 let id = id.into();
963 info!("Cloning playlist with id: {id}");
964
965 let new_playlist = Playlist::create_copy(&self.db, id)
966 .await?
967 .ok_or(Error::NotFound)?;
968
969 Ok(new_playlist.id.into())
970 }
971 #[instrument]
974 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
975 info!("Getting playlist ID: {name}");
976
977 Playlist::read_by_name(&self.db, name)
978 .await
979 .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
980 .ok()
981 .flatten()
982 .map(|playlist| playlist.id.into())
983 }
984 #[instrument]
987 async fn playlist_remove_songs(
988 self,
989 context: Context,
990 playlist: PlaylistId,
991 songs: Vec<SongId>,
992 ) -> Result<(), SerializableLibraryError> {
993 let playlist = playlist.into();
994 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
995 info!("Removing song from playlist: {playlist} ({songs:?})");
996
997 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
998 }
999 #[instrument]
1002 async fn playlist_add(
1003 self,
1004 context: Context,
1005 playlist: PlaylistId,
1006 thing: schemas::RecordId,
1007 ) -> Result<(), SerializableLibraryError> {
1008 let playlist = playlist.into();
1009 info!("Adding thing to playlist: {playlist} ({thing})");
1010
1011 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1013
1014 Ok(Playlist::add_songs(
1015 &self.db,
1016 playlist,
1017 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1018 )
1019 .await?)
1020 }
1021 #[instrument]
1024 async fn playlist_add_list(
1025 self,
1026 context: Context,
1027 playlist: PlaylistId,
1028 list: Vec<schemas::RecordId>,
1029 ) -> Result<(), SerializableLibraryError> {
1030 let playlist = playlist.into();
1031 info!(
1032 "Adding list to playlist: {playlist} ({})",
1033 list.iter()
1034 .map(ToString::to_string)
1035 .collect::<Vec<_>>()
1036 .join(", ")
1037 );
1038
1039 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1041
1042 Ok(Playlist::add_songs(
1043 &self.db,
1044 playlist,
1045 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1046 )
1047 .await?)
1048 }
1049 #[instrument]
1051 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1052 let id = id.into();
1053 info!("Getting playlist by ID: {id}");
1054
1055 Playlist::read(&self.db, id)
1056 .await
1057 .tap_err(|e| warn!("Error in playlist_get: {e}"))
1058 .ok()
1059 .flatten()
1060 }
1061 #[instrument]
1063 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1064 let id = id.into();
1065 info!("Getting songs in: {id}");
1066 Playlist::read_songs(&self.db, id)
1067 .await
1068 .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1069 .ok()
1070 .map(Into::into)
1071 }
1072 #[instrument]
1074 async fn playlist_rename(
1075 self,
1076 context: Context,
1077 id: PlaylistId,
1078 name: String,
1079 ) -> Result<Playlist, SerializableLibraryError> {
1080 let id = id.into();
1081 info!("Renaming playlist: {id} ({name})");
1082 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1083 .await?
1084 .ok_or(Error::NotFound.into())
1085 }
1086
1087 #[instrument]
1089 async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1090 info!("Listing collections");
1091 Collection::read_all(&self.db)
1092 .await
1093 .tap_err(|e| warn!("Error in collection_list: {e}"))
1094 .ok()
1095 .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1096 .unwrap_or_default()
1097 }
1098 #[instrument]
1100 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1101 info!("Getting collection by ID: {id:?}");
1102 Collection::read(&self.db, id.into())
1103 .await
1104 .tap_err(|e| warn!("Error in collection_get: {e}"))
1105 .ok()
1106 .flatten()
1107 }
1108 #[instrument]
1110 async fn collection_freeze(
1111 self,
1112 context: Context,
1113 id: CollectionId,
1114 name: String,
1115 ) -> Result<PlaylistId, SerializableLibraryError> {
1116 info!("Freezing collection: {id:?} ({name})");
1117 Ok(Collection::freeze(&self.db, id.into(), name)
1118 .await
1119 .map(|p| p.id.into())?)
1120 }
1121 #[instrument]
1123 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1124 let id = id.into();
1125 info!("Getting songs in: {id}");
1126 Collection::read_songs(&self.db, id)
1127 .await
1128 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1129 .ok()
1130 .map(Into::into)
1131 }
1132
1133 #[instrument]
1135 async fn radio_get_similar(
1136 self,
1137 context: Context,
1138 things: Vec<schemas::RecordId>,
1139 n: u32,
1140 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1141 #[cfg(not(feature = "analysis"))]
1142 {
1143 warn!("Analysis is not enabled");
1144 return Err(SerializableLibraryError::AnalysisNotEnabled);
1145 }
1146
1147 #[cfg(feature = "analysis")]
1148 {
1149 info!("Getting the {n} most similar songs to: {things:?}");
1150 Ok(services::radio::get_similar(&self.db, things, n)
1151 .await
1152 .map(Vec::into_boxed_slice)
1153 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1154 }
1155 }
1156 #[instrument]
1158 async fn radio_get_similar_ids(
1159 self,
1160 context: Context,
1161 things: Vec<schemas::RecordId>,
1162 n: u32,
1163 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1164 #[cfg(not(feature = "analysis"))]
1165 {
1166 warn!("Analysis is not enabled");
1167 return Err(SerializableLibraryError::AnalysisNotEnabled);
1168 }
1169
1170 #[cfg(feature = "analysis")]
1171 {
1172 info!("Getting the {n} most similar songs to: {things:?}");
1173 Ok(services::radio::get_similar(&self.db, things, n)
1174 .await
1175 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1176 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1177 }
1178 }
1179
1180 #[instrument]
1183 async fn dynamic_playlist_create(
1184 self,
1185 context: Context,
1186 name: String,
1187 query: Query,
1188 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1189 let id = DynamicPlaylist::generate_id();
1190 info!("Creating new DP: {id:?} ({name})");
1191
1192 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1193 .await
1194 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1195 {
1196 Some(dp) => Ok(dp.id.into()),
1197 None => Err(Error::NotCreated.into()),
1198 }
1199 }
1200 #[instrument]
1202 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1203 info!("Listing DPs");
1204 DynamicPlaylist::read_all(&self.db)
1205 .await
1206 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1207 .ok()
1208 .map(Into::into)
1209 .unwrap_or_default()
1210 }
1211 #[instrument]
1213 async fn dynamic_playlist_update(
1214 self,
1215 context: Context,
1216 id: DynamicPlaylistId,
1217 changes: DynamicPlaylistChangeSet,
1218 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1219 info!("Updating DP: {id:?}, {changes:?}");
1220 DynamicPlaylist::update(&self.db, id.into(), changes)
1221 .await
1222 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1223 .ok_or(Error::NotFound.into())
1224 }
1225 #[instrument]
1227 async fn dynamic_playlist_remove(
1228 self,
1229 context: Context,
1230 id: DynamicPlaylistId,
1231 ) -> Result<(), SerializableLibraryError> {
1232 info!("Removing DP with id: {id:?}");
1233 DynamicPlaylist::delete(&self.db, id.into())
1234 .await?
1235 .ok_or(Error::NotFound)?;
1236 Ok(())
1237 }
1238 #[instrument]
1240 async fn dynamic_playlist_get(
1241 self,
1242 context: Context,
1243 id: DynamicPlaylistId,
1244 ) -> Option<DynamicPlaylist> {
1245 info!("Getting DP by ID: {id:?}");
1246 DynamicPlaylist::read(&self.db, id.into())
1247 .await
1248 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1249 .ok()
1250 .flatten()
1251 }
1252 #[instrument]
1254 async fn dynamic_playlist_get_songs(
1255 self,
1256 context: Context,
1257 id: DynamicPlaylistId,
1258 ) -> Option<Box<[Song]>> {
1259 info!("Getting songs in DP: {id:?}");
1260 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1261 .await
1262 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1263 .ok()
1264 .flatten()
1265 .map(Into::into)
1266 }
1267}