1use std::{fs::File, 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::{BackupError, 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 self,
46 backup::{
47 export_dynamic_playlists, export_playlist, import_dynamic_playlists, import_playlist,
48 validate_file_path,
49 },
50 },
51 termination::{self, Terminator},
52};
53
54#[derive(Clone, Debug)]
55pub struct MusicPlayerServer {
56 db: Arc<Surreal<Db>>,
57 settings: Arc<Settings>,
58 audio_kernel: Arc<AudioKernelSender>,
59 library_rescan_lock: Arc<Mutex<()>>,
60 library_analyze_lock: Arc<Mutex<()>>,
61 collection_recluster_lock: Arc<Mutex<()>>,
62 publisher: Arc<RwLock<Sender<Message>>>,
63 terminator: Arc<Mutex<Terminator>>,
64 interrupt: Arc<termination::InterruptReceiver>,
65}
66
67impl MusicPlayerServer {
68 #[must_use]
69 #[inline]
70 pub fn new(
71 db: Arc<Surreal<Db>>,
72 settings: Arc<Settings>,
73 audio_kernel: Arc<AudioKernelSender>,
74 event_publisher: Arc<RwLock<Sender<Message>>>,
75 terminator: Terminator,
76 interrupt: termination::InterruptReceiver,
77 ) -> Self {
78 Self {
79 db,
80 publisher: event_publisher,
81 settings,
82 audio_kernel,
83 library_rescan_lock: Arc::new(Mutex::new(())),
84 library_analyze_lock: Arc::new(Mutex::new(())),
85 collection_recluster_lock: Arc::new(Mutex::new(())),
86 terminator: Arc::new(Mutex::new(terminator)),
87 interrupt: Arc::new(interrupt),
88 }
89 }
90
91 #[instrument]
97 pub async fn publish(
98 &self,
99 message: impl Into<Message> + Send + Sync + std::fmt::Debug,
100 ) -> Result<(), mecomp_core::errors::UdpError> {
101 self.publisher.read().await.send(message).await
102 }
103}
104
105#[allow(clippy::missing_inline_in_public_items)]
106impl MusicPlayer for MusicPlayerServer {
107 #[instrument]
108 async fn register_listener(self, context: Context, listener_addr: std::net::SocketAddr) {
109 info!("Registering listener: {listener_addr}");
110 self.publisher.write().await.add_subscriber(listener_addr);
111 }
112
113 async fn ping(self, _: Context) -> String {
114 "pong".to_string()
115 }
116
117 #[instrument]
119 async fn library_rescan(self, context: Context) -> Result<(), SerializableLibraryError> {
120 info!("Rescanning library");
121
122 if self.library_rescan_lock.try_lock().is_err() {
123 warn!("Library rescan already in progress");
124 return Err(SerializableLibraryError::RescanInProgress);
125 }
126
127 let span = tracing::Span::current();
128
129 tokio::task::spawn(
130 async move {
131 let _guard = self.library_rescan_lock.lock().await;
132 match services::library::rescan(
133 &self.db,
134 &self.settings.daemon.library_paths,
135 &self.settings.daemon.artist_separator,
136 &self.settings.daemon.protected_artist_names,
137 self.settings.daemon.genre_separator.as_deref(),
138 self.settings.daemon.conflict_resolution,
139 )
140 .await
141 {
142 Ok(()) => info!("Library rescan complete"),
143 Err(e) => error!("Error in library_rescan: {e}"),
144 }
145
146 let result = self.publish(Event::LibraryRescanFinished).await;
147 if let Err(e) = result {
148 error!("Error notifying clients that library_rescan_finished: {e}");
149 }
150 }
151 .instrument(span),
152 );
153
154 Ok(())
155 }
156 #[instrument]
158 async fn library_rescan_in_progress(self, context: Context) -> bool {
159 self.library_rescan_lock.try_lock().is_err()
160 }
161 #[instrument]
163 async fn library_analyze(
164 self,
165 context: Context,
166 overwrite: bool,
167 ) -> Result<(), SerializableLibraryError> {
168 #[cfg(not(feature = "analysis"))]
169 {
170 warn!("Analysis is not enabled");
171 return Err(SerializableLibraryError::AnalysisNotEnabled);
172 }
173
174 #[cfg(feature = "analysis")]
175 {
176 info!("Analyzing library");
177
178 if self.library_analyze_lock.try_lock().is_err() {
179 warn!("Library analysis already in progress");
180 return Err(SerializableLibraryError::AnalysisInProgress);
181 }
182 let span = tracing::Span::current();
183
184 tokio::task::spawn(
185 async move {
186 let _guard = self.library_analyze_lock.lock().await;
187 match services::library::analyze(
188 &self.db,
189 self.interrupt.resubscribe(),
190 overwrite,
191 )
192 .await
193 {
194 Ok(()) => info!("Library analysis complete"),
195 Err(e) => error!("Error in library_analyze: {e}"),
196 }
197
198 let result = self.publish(Event::LibraryAnalysisFinished).await;
199 if let Err(e) = result {
200 error!("Error notifying clients that library_analysis_finished: {e}");
201 }
202 }
203 .instrument(span),
204 );
205
206 Ok(())
207 }
208 }
209 #[instrument]
211 async fn library_analyze_in_progress(self, context: Context) -> bool {
212 self.library_analyze_lock.try_lock().is_err()
213 }
214 #[instrument]
216 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
217 #[cfg(not(feature = "analysis"))]
218 {
219 warn!("Analysis is not enabled");
220 return Err(SerializableLibraryError::AnalysisNotEnabled);
221 }
222
223 #[cfg(feature = "analysis")]
224 {
225 info!("Reclustering collections");
226
227 if self.collection_recluster_lock.try_lock().is_err() {
228 warn!("Collection reclustering already in progress");
229 return Err(SerializableLibraryError::ReclusterInProgress);
230 }
231
232 let span = tracing::Span::current();
233
234 tokio::task::spawn(
235 async move {
236 let _guard = self.collection_recluster_lock.lock().await;
237 match services::library::recluster(
238 &self.db,
239 self.settings.reclustering,
240 self.interrupt.resubscribe(),
241 )
242 .await
243 {
244 Ok(()) => info!("Collection reclustering complete"),
245 Err(e) => error!("Error in library_recluster: {e}"),
246 }
247
248 let result = self.publish(Event::LibraryReclusterFinished).await;
249 if let Err(e) = result {
250 error!("Error notifying clients that library_recluster_finished: {e}");
251 }
252 }
253 .instrument(span),
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 #[instrument]
1089 async fn playlist_export(
1090 self,
1091 context: Context,
1092 id: PlaylistId,
1093 path: PathBuf,
1094 ) -> Result<(), SerializableLibraryError> {
1095 info!("Exporting playlist to: {}", path.display());
1096
1097 validate_file_path(&path, "m3u", false)?;
1099
1100 let playlist = Playlist::read(&self.db, id.into())
1102 .await
1103 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1104 .ok()
1105 .flatten()
1106 .ok_or(Error::NotFound)?;
1107 let songs = Playlist::read_songs(&self.db, playlist.id)
1109 .await
1110 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1111 .ok()
1112 .unwrap_or_default();
1113
1114 let file = File::create(&path).tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1116 export_playlist(&playlist.name, &songs, file)
1118 .tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1119 info!("Exported playlist to: {path:?}");
1120 Ok(())
1121 }
1122 #[instrument]
1124 async fn playlist_import(
1125 self,
1126 context: Context,
1127 path: PathBuf,
1128 name: Option<String>,
1129 ) -> Result<PlaylistId, SerializableLibraryError> {
1130 info!("Importing playlist from: {}", path.display());
1131
1132 validate_file_path(&path, "m3u", true)?;
1134
1135 let file = File::open(&path).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1137 let (parsed_name, song_paths) =
1138 import_playlist(file).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1139
1140 log::debug!("Parsed playlist name: {parsed_name:?}");
1141 log::debug!("Parsed song paths: {song_paths:?}");
1142
1143 let name = match (name, parsed_name) {
1144 (Some(name), _) | (None, Some(name)) => name,
1145 (None, None) => "Imported Playlist".to_owned(),
1146 };
1147
1148 if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1150 info!("Playlist \"{name}\" already exists, will not import");
1152 return Ok(playlist.id.into());
1153 }
1154
1155 let playlist = Playlist::create(
1157 &self.db,
1158 Playlist {
1159 id: Playlist::generate_id(),
1160 name,
1161 runtime: Duration::from_secs(0),
1162 song_count: 0,
1163 },
1164 )
1165 .await
1166 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1167 .ok_or(Error::NotCreated)?;
1168
1169 let mut songs = Vec::new();
1171 for path in &song_paths {
1172 let Some(song) = Song::read_by_path(&self.db, path.clone())
1173 .await
1174 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1175 else {
1176 warn!("Song at {} not found in the library", path.display());
1177 continue;
1178 };
1179
1180 songs.push(song.id);
1181 }
1182
1183 if songs.is_empty() {
1184 return Err(BackupError::NoValidSongs(song_paths.len()).into());
1185 }
1186
1187 Playlist::add_songs(&self.db, playlist.id.clone(), songs)
1189 .await
1190 .tap_err(|e| {
1191 warn!("Error in playlist_import: {e}");
1192 })?;
1193
1194 Ok(playlist.id.into())
1196 }
1197
1198 #[instrument]
1200 async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1201 info!("Listing collections");
1202 Collection::read_all(&self.db)
1203 .await
1204 .tap_err(|e| warn!("Error in collection_list: {e}"))
1205 .ok()
1206 .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1207 .unwrap_or_default()
1208 }
1209 #[instrument]
1211 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1212 info!("Getting collection by ID: {id:?}");
1213 Collection::read(&self.db, id.into())
1214 .await
1215 .tap_err(|e| warn!("Error in collection_get: {e}"))
1216 .ok()
1217 .flatten()
1218 }
1219 #[instrument]
1221 async fn collection_freeze(
1222 self,
1223 context: Context,
1224 id: CollectionId,
1225 name: String,
1226 ) -> Result<PlaylistId, SerializableLibraryError> {
1227 info!("Freezing collection: {id:?} ({name})");
1228 Ok(Collection::freeze(&self.db, id.into(), name)
1229 .await
1230 .map(|p| p.id.into())?)
1231 }
1232 #[instrument]
1234 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1235 let id = id.into();
1236 info!("Getting songs in: {id}");
1237 Collection::read_songs(&self.db, id)
1238 .await
1239 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1240 .ok()
1241 .map(Into::into)
1242 }
1243
1244 #[instrument]
1246 async fn radio_get_similar(
1247 self,
1248 context: Context,
1249 things: Vec<schemas::RecordId>,
1250 n: u32,
1251 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1252 #[cfg(not(feature = "analysis"))]
1253 {
1254 warn!("Analysis is not enabled");
1255 return Err(SerializableLibraryError::AnalysisNotEnabled);
1256 }
1257
1258 #[cfg(feature = "analysis")]
1259 {
1260 info!("Getting the {n} most similar songs to: {things:?}");
1261 Ok(services::radio::get_similar(&self.db, things, n)
1262 .await
1263 .map(Vec::into_boxed_slice)
1264 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1265 }
1266 }
1267 #[instrument]
1269 async fn radio_get_similar_ids(
1270 self,
1271 context: Context,
1272 things: Vec<schemas::RecordId>,
1273 n: u32,
1274 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1275 #[cfg(not(feature = "analysis"))]
1276 {
1277 warn!("Analysis is not enabled");
1278 return Err(SerializableLibraryError::AnalysisNotEnabled);
1279 }
1280
1281 #[cfg(feature = "analysis")]
1282 {
1283 info!("Getting the {n} most similar songs to: {things:?}");
1284 Ok(services::radio::get_similar(&self.db, things, n)
1285 .await
1286 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1287 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1288 }
1289 }
1290
1291 #[instrument]
1294 async fn dynamic_playlist_create(
1295 self,
1296 context: Context,
1297 name: String,
1298 query: Query,
1299 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1300 let id = DynamicPlaylist::generate_id();
1301 info!("Creating new DP: {id:?} ({name})");
1302
1303 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1304 .await
1305 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1306 {
1307 Some(dp) => Ok(dp.id.into()),
1308 None => Err(Error::NotCreated.into()),
1309 }
1310 }
1311 #[instrument]
1313 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1314 info!("Listing DPs");
1315 DynamicPlaylist::read_all(&self.db)
1316 .await
1317 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1318 .ok()
1319 .map(Into::into)
1320 .unwrap_or_default()
1321 }
1322 #[instrument]
1324 async fn dynamic_playlist_update(
1325 self,
1326 context: Context,
1327 id: DynamicPlaylistId,
1328 changes: DynamicPlaylistChangeSet,
1329 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1330 info!("Updating DP: {id:?}, {changes:?}");
1331 DynamicPlaylist::update(&self.db, id.into(), changes)
1332 .await
1333 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1334 .ok_or(Error::NotFound.into())
1335 }
1336 #[instrument]
1338 async fn dynamic_playlist_remove(
1339 self,
1340 context: Context,
1341 id: DynamicPlaylistId,
1342 ) -> Result<(), SerializableLibraryError> {
1343 info!("Removing DP with id: {id:?}");
1344 DynamicPlaylist::delete(&self.db, id.into())
1345 .await?
1346 .ok_or(Error::NotFound)?;
1347 Ok(())
1348 }
1349 #[instrument]
1351 async fn dynamic_playlist_get(
1352 self,
1353 context: Context,
1354 id: DynamicPlaylistId,
1355 ) -> Option<DynamicPlaylist> {
1356 info!("Getting DP by ID: {id:?}");
1357 DynamicPlaylist::read(&self.db, id.into())
1358 .await
1359 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1360 .ok()
1361 .flatten()
1362 }
1363 #[instrument]
1365 async fn dynamic_playlist_get_songs(
1366 self,
1367 context: Context,
1368 id: DynamicPlaylistId,
1369 ) -> Option<Box<[Song]>> {
1370 info!("Getting songs in DP: {id:?}");
1371 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1372 .await
1373 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1374 .ok()
1375 .flatten()
1376 .map(Into::into)
1377 }
1378 #[instrument]
1380 async fn dynamic_playlist_export(
1381 self,
1382 context: Context,
1383 path: PathBuf,
1384 ) -> Result<(), SerializableLibraryError> {
1385 info!("Exporting dynamic playlists to: {path:?}");
1386
1387 validate_file_path(&path, "csv", false)?;
1389
1390 let playlists = DynamicPlaylist::read_all(&self.db)
1392 .await
1393 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1394
1395 let file =
1397 File::create(&path).tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1398 let writer = csv::Writer::from_writer(std::io::BufWriter::new(file));
1399 export_dynamic_playlists(&playlists, writer)
1401 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1402 info!("Exported dynamic playlists to: {path:?}");
1403 Ok(())
1404 }
1405 #[instrument]
1407 async fn dynamic_playlist_import(
1408 self,
1409 context: Context,
1410 path: PathBuf,
1411 ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError> {
1412 info!("Importing dynamic playlists from: {path:?}");
1413
1414 validate_file_path(&path, "csv", true)?;
1416
1417 let file = File::open(&path).tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1419 let reader = csv::ReaderBuilder::new()
1420 .has_headers(true)
1421 .from_reader(std::io::BufReader::new(file));
1422
1423 let playlists = import_dynamic_playlists(reader)
1425 .tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1426
1427 if playlists.is_empty() {
1428 return Err(BackupError::NoValidPlaylists.into());
1429 }
1430
1431 let mut ids = Vec::new();
1433 for playlist in playlists {
1434 if let Ok(Some(existing_playlist)) =
1436 DynamicPlaylist::read_by_name(&self.db, playlist.name.clone()).await
1437 {
1438 info!(
1439 "Dynamic Playlist \"{}\" already exists, will not import",
1440 existing_playlist.name
1441 );
1442 continue;
1443 }
1444
1445 ids.push(
1446 DynamicPlaylist::create(&self.db, playlist)
1447 .await
1448 .tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?
1449 .ok_or(Error::NotCreated)?,
1450 );
1451 }
1452
1453 Ok(ids)
1454 }
1455}