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}
65
66impl MusicPlayerServer {
67 #[must_use]
68 #[inline]
69 pub fn new(
70 db: Arc<Surreal<Db>>,
71 settings: Arc<Settings>,
72 audio_kernel: Arc<AudioKernelSender>,
73 event_publisher: Arc<RwLock<Sender<Message>>>,
74 terminator: Terminator,
75 ) -> Self {
76 Self {
77 db,
78 publisher: event_publisher,
79 settings,
80 audio_kernel,
81 library_rescan_lock: Arc::new(Mutex::new(())),
82 library_analyze_lock: Arc::new(Mutex::new(())),
83 collection_recluster_lock: Arc::new(Mutex::new(())),
84 terminator: Arc::new(Mutex::new(terminator)),
85 }
86 }
87
88 #[instrument]
94 pub async fn publish(
95 &self,
96 message: impl Into<Message> + Send + Sync + std::fmt::Debug,
97 ) -> Result<(), mecomp_core::errors::UdpError> {
98 self.publisher.read().await.send(message).await
99 }
100}
101
102#[allow(clippy::missing_inline_in_public_items)]
103impl MusicPlayer for MusicPlayerServer {
104 #[instrument]
105 async fn register_listener(self, context: Context, listener_addr: std::net::SocketAddr) {
106 info!("Registering listener: {listener_addr}");
107 self.publisher.write().await.add_subscriber(listener_addr);
108 }
109
110 async fn ping(self, _: Context) -> String {
111 "pong".to_string()
112 }
113
114 #[instrument]
116 async fn library_rescan(self, context: Context) -> Result<(), SerializableLibraryError> {
117 info!("Rescanning library");
118
119 if self.library_rescan_lock.try_lock().is_err() {
120 warn!("Library rescan already in progress");
121 return Err(SerializableLibraryError::RescanInProgress);
122 }
123
124 let span = tracing::Span::current();
125
126 std::thread::Builder::new()
127 .name(String::from("Library Rescan"))
128 .spawn(move || {
129 futures::executor::block_on(
130 async {
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
155 Ok(())
156 }
157 #[instrument]
159 async fn library_rescan_in_progress(self, context: Context) -> bool {
160 self.library_rescan_lock.try_lock().is_err()
161 }
162 #[instrument]
164 async fn library_analyze(
165 self,
166 context: Context,
167 overwrite: bool,
168 ) -> Result<(), SerializableLibraryError> {
169 #[cfg(not(feature = "analysis"))]
170 {
171 warn!("Analysis is not enabled");
172 return Err(SerializableLibraryError::AnalysisNotEnabled);
173 }
174
175 #[cfg(feature = "analysis")]
176 {
177 info!("Analyzing library");
178
179 if self.library_analyze_lock.try_lock().is_err() {
180 warn!("Library analysis already in progress");
181 return Err(SerializableLibraryError::AnalysisInProgress);
182 }
183 let span = tracing::Span::current();
184
185 std::thread::Builder::new()
186 .name(String::from("Library Analysis"))
187 .spawn(move || {
188 futures::executor::block_on(
189 async {
190 let _guard = self.library_analyze_lock.lock().await;
191 match services::library::analyze(&self.db, overwrite).await {
192 Ok(()) => info!("Library analysis complete"),
193 Err(e) => error!("Error in library_analyze: {e}"),
194 }
195
196 let result = &self.publish(Event::LibraryAnalysisFinished).await;
197 if let Err(e) = result {
198 error!(
199 "Error notifying clients that library_analysis_finished: {e}"
200 );
201 }
202 }
203 .instrument(span),
204 );
205 })?;
206
207 Ok(())
208 }
209 }
210 #[instrument]
212 async fn library_analyze_in_progress(self, context: Context) -> bool {
213 self.library_analyze_lock.try_lock().is_err()
214 }
215 #[instrument]
217 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
218 #[cfg(not(feature = "analysis"))]
219 {
220 warn!("Analysis is not enabled");
221 return Err(SerializableLibraryError::AnalysisNotEnabled);
222 }
223
224 #[cfg(feature = "analysis")]
225 {
226 info!("Reclustering collections");
227
228 if self.collection_recluster_lock.try_lock().is_err() {
229 warn!("Collection reclustering already in progress");
230 return Err(SerializableLibraryError::ReclusterInProgress);
231 }
232
233 let span = tracing::Span::current();
234
235 std::thread::Builder::new()
236 .name(String::from("Collection Recluster"))
237 .spawn(move || {
238 futures::executor::block_on(
239 async {
240 let _guard = self.collection_recluster_lock.lock().await;
241 match services::library::recluster(
242 &self.db,
243 &self.settings.reclustering,
244 )
245 .await
246 {
247 Ok(()) => info!("Collection reclustering complete"),
248 Err(e) => error!("Error in collection_recluster: {e}"),
249 }
250
251 let result = &self.publish(Event::LibraryReclusterFinished).await;
252 if let Err(e) = result {
253 error!(
254 "Error notifying clients that library_recluster_finished: {e}"
255 );
256 }
257 }
258 .instrument(span),
259 );
260 })?;
261
262 Ok(())
263 }
264 }
265 #[instrument]
267 async fn library_recluster_in_progress(self, context: Context) -> bool {
268 self.collection_recluster_lock.try_lock().is_err()
269 }
270 #[instrument]
272 async fn library_brief(
273 self,
274 context: Context,
275 ) -> Result<LibraryBrief, SerializableLibraryError> {
276 info!("Creating library brief");
277 Ok(services::library::brief(&self.db)
278 .await
279 .tap_err(|e| warn!("Error in library_brief: {e}"))?)
280 }
281 #[instrument]
283 async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
284 info!("Creating library full");
285 Ok(services::library::full(&self.db)
286 .await
287 .tap_err(|e| warn!("Error in library_full: {e}"))?)
288 }
289 #[instrument]
291 async fn library_artists_brief(
292 self,
293 context: Context,
294 ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
295 info!("Creating library artists brief");
296 Ok(Artist::read_all(&self.db)
297 .await
298 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
299 .iter()
300 .map(std::convert::Into::into)
301 .collect())
302 }
303 #[instrument]
305 async fn library_artists_full(
306 self,
307 context: Context,
308 ) -> Result<Box<[Artist]>, SerializableLibraryError> {
309 info!("Creating library artists full");
310 Ok(Artist::read_all(&self.db)
311 .await
312 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
313 .into_boxed_slice())
314 }
315 #[instrument]
317 async fn library_albums_brief(
318 self,
319 context: Context,
320 ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
321 info!("Creating library albums brief");
322 Ok(Album::read_all(&self.db)
323 .await
324 .tap_err(|e| warn!("Error in library_albums_brief: {e}"))?
325 .iter()
326 .map(std::convert::Into::into)
327 .collect())
328 }
329 #[instrument]
331 async fn library_albums_full(
332 self,
333 context: Context,
334 ) -> Result<Box<[Album]>, SerializableLibraryError> {
335 info!("Creating library albums full");
336 Ok(Album::read_all(&self.db)
337 .await
338 .map(std::vec::Vec::into_boxed_slice)
339 .tap_err(|e| warn!("Error in library_albums_full: {e}"))?)
340 }
341 #[instrument]
343 async fn library_songs_brief(
344 self,
345 context: Context,
346 ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
347 info!("Creating library songs brief");
348 Ok(Song::read_all(&self.db)
349 .await
350 .tap_err(|e| warn!("Error in library_songs_brief: {e}"))?
351 .iter()
352 .map(std::convert::Into::into)
353 .collect())
354 }
355 #[instrument]
357 async fn library_songs_full(
358 self,
359 context: Context,
360 ) -> Result<Box<[Song]>, SerializableLibraryError> {
361 info!("Creating library songs full");
362 Ok(Song::read_all(&self.db)
363 .await
364 .map(std::vec::Vec::into_boxed_slice)
365 .tap_err(|e| warn!("Error in library_songs_full: {e}"))?)
366 }
367 #[instrument]
369 async fn library_health(
370 self,
371 context: Context,
372 ) -> Result<LibraryHealth, SerializableLibraryError> {
373 info!("Creating library health");
374 Ok(services::library::health(&self.db)
375 .await
376 .tap_err(|e| warn!("Error in library_health: {e}"))?)
377 }
378 #[instrument]
380 async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
381 let id = id.into();
382 info!("Getting song by ID: {id}");
383 Song::read(&self.db, id)
384 .await
385 .tap_err(|e| warn!("Error in library_song_get: {e}"))
386 .ok()
387 .flatten()
388 }
389 #[instrument]
391 async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
392 info!("Getting song by path: {}", path.display());
393 Song::read_by_path(&self.db, path)
394 .await
395 .tap_err(|e| warn!("Error in library_song_get_by_path: {e}"))
396 .ok()
397 .flatten()
398 }
399 #[instrument]
401 async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
402 let id = id.into();
403 info!("Getting artist of: {id}");
404 Song::read_artist(&self.db, id)
405 .await
406 .tap_err(|e| warn!("Error in library_song_get_artist: {e}"))
407 .ok()
408 .into()
409 }
410 #[instrument]
412 async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
413 let id = id.into();
414 info!("Getting album of: {id}");
415 Song::read_album(&self.db, id)
416 .await
417 .tap_err(|e| warn!("Error in library_song_get_album: {e}"))
418 .ok()
419 .flatten()
420 }
421 #[instrument]
423 async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
424 let id = id.into();
425 info!("Getting playlists of: {id}");
426 Song::read_playlists(&self.db, id)
427 .await
428 .tap_err(|e| warn!("Error in library_song_get_playlists: {e}"))
429 .ok()
430 .unwrap_or_default()
431 .into()
432 }
433 #[instrument]
435 async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
436 let id = id.into();
437 info!("Getting collections of: {id}");
438 Song::read_collections(&self.db, id)
439 .await
440 .tap_err(|e| warn!("Error in library_song_get_collections: {e}"))
441 .ok()
442 .unwrap_or_default()
443 .into()
444 }
445
446 #[instrument]
448 async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
449 let id = id.into();
450 info!("Getting album by ID: {id}");
451 Album::read(&self.db, id)
452 .await
453 .tap_err(|e| warn!("Error in library_album_get: {e}"))
454 .ok()
455 .flatten()
456 }
457 #[instrument]
459 async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
460 let id = id.into();
461 info!("Getting artists of: {id}");
462 Album::read_artist(&self.db, id)
463 .await
464 .tap_err(|e| warn!("Error in library_album_get_artist: {e}"))
465 .ok()
466 .into()
467 }
468 #[instrument]
470 async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
471 let id = id.into();
472 info!("Getting songs of: {id}");
473 Album::read_songs(&self.db, id)
474 .await
475 .tap_err(|e| warn!("Error in library_album_get_songs: {e}"))
476 .ok()
477 .map(Into::into)
478 }
479 #[instrument]
481 async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
482 let id = id.into();
483 info!("Getting artist by ID: {id}");
484 Artist::read(&self.db, id)
485 .await
486 .tap_err(|e| warn!("Error in library_artist_get: {e}"))
487 .ok()
488 .flatten()
489 }
490 #[instrument]
492 async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
493 let id = id.into();
494 info!("Getting songs of: {id}");
495 Artist::read_songs(&self.db, id)
496 .await
497 .tap_err(|e| warn!("Error in library_artist_get_songs: {e}"))
498 .ok()
499 .map(Into::into)
500 }
501 #[instrument]
503 async fn library_artist_get_albums(
504 self,
505 context: Context,
506 id: ArtistId,
507 ) -> Option<Box<[Album]>> {
508 let id = id.into();
509 info!("Getting albums of: {id}");
510 Artist::read_albums(&self.db, id)
511 .await
512 .tap_err(|e| warn!("Error in library_artist_get_albums: {e}"))
513 .ok()
514 .map(Into::into)
515 }
516
517 #[instrument]
519 async fn daemon_shutdown(self, context: Context) {
520 let terminator = self.terminator.clone();
521 std::thread::Builder::new()
522 .name(String::from("Daemon Shutdown"))
523 .spawn(move || {
524 std::thread::sleep(std::time::Duration::from_secs(1));
525 let terminate_result = terminator
526 .blocking_lock()
527 .terminate(termination::Interrupted::UserInt);
528 if let Err(e) = terminate_result {
529 error!("Error terminating daemon, panicking instead: {e}");
530 panic!("Error terminating daemon: {e}");
531 }
532 })
533 .unwrap();
534 info!("Shutting down daemon in 1 second");
535 }
536
537 #[instrument]
539 async fn state_audio(self, context: Context) -> Option<StateAudio> {
540 debug!("Getting state of audio player");
541 let (tx, rx) = tokio::sync::oneshot::channel();
542
543 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
544
545 rx.await
546 .tap_err(|e| warn!("Error in state_audio: {e}"))
547 .ok()
548 }
549
550 #[instrument]
552 async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
553 info!("Getting current artist");
554 let (tx, rx) = tokio::sync::oneshot::channel();
555
556 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
557
558 if let Some(song) = rx
559 .await
560 .tap_err(|e| warn!("Error in current_artist: {e}"))
561 .ok()
562 .and_then(|state| state.current_song)
563 {
564 Song::read_artist(&self.db, song.id)
565 .await
566 .tap_err(|e| warn!("Error in current_album: {e}"))
567 .ok()
568 .into()
569 } else {
570 OneOrMany::None
571 }
572 }
573 #[instrument]
575 async fn current_album(self, context: Context) -> Option<Album> {
576 info!("Getting current album");
577 let (tx, rx) = tokio::sync::oneshot::channel();
578
579 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
580
581 if let Some(song) = rx
582 .await
583 .tap_err(|e| warn!("Error in current_album: {e}"))
584 .ok()
585 .and_then(|state| state.current_song)
586 {
587 Song::read_album(&self.db, song.id)
588 .await
589 .tap_err(|e| warn!("Error in current_album: {e}"))
590 .ok()
591 .flatten()
592 } else {
593 None
594 }
595 }
596 #[instrument]
598 async fn current_song(self, context: Context) -> Option<Song> {
599 info!("Getting current song");
600 let (tx, rx) = tokio::sync::oneshot::channel();
601
602 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
603
604 rx.await
605 .tap_err(|e| warn!("Error in current_song: {e}"))
606 .ok()
607 .and_then(|state| state.current_song)
608 }
609
610 #[instrument]
612 async fn rand_artist(self, context: Context) -> Option<Artist> {
613 info!("Getting random artist");
614 Artist::read_all(&self.db)
615 .await
616 .tap_err(|e| warn!("Error in rand_artist: {e}"))
617 .ok()
618 .and_then(|artists| artists.choose(&mut rand::thread_rng()).cloned())
619 }
620 #[instrument]
622 async fn rand_album(self, context: Context) -> Option<Album> {
623 info!("Getting random album");
624 Album::read_all(&self.db)
625 .await
626 .tap_err(|e| warn!("Error in rand_album: {e}"))
627 .ok()
628 .and_then(|albums| albums.choose(&mut rand::thread_rng()).cloned())
629 }
630 #[instrument]
632 async fn rand_song(self, context: Context) -> Option<Song> {
633 info!("Getting random song");
634 Song::read_all(&self.db)
635 .await
636 .tap_err(|e| warn!("Error in rand_song: {e}"))
637 .ok()
638 .and_then(|songs| songs.choose(&mut rand::thread_rng()).cloned())
639 }
640
641 #[instrument]
643 async fn search(self, context: Context, query: String, limit: u32) -> SearchResult {
644 info!("Searching for: {query}");
645 let songs = Song::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 albums = Album::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
662 let artists = Artist::search(&self.db, &query, i64::from(limit))
663 .await
664 .tap_err(|e| warn!("Error in search: {e}"))
665 .unwrap_or_default()
666 .into();
667 SearchResult {
668 songs,
669 albums,
670 artists,
671 }
672 }
673 #[instrument]
675 async fn search_artist(self, context: Context, query: String, limit: u32) -> Box<[Artist]> {
676 info!("Searching for artist: {query}");
677 Artist::search(&self.db, &query, i64::from(limit))
678 .await
679 .tap_err(|e| {
680 warn!("Error in search_artist: {e}");
681 })
682 .unwrap_or_default()
683 .into()
684 }
685 #[instrument]
687 async fn search_album(self, context: Context, query: String, limit: u32) -> Box<[Album]> {
688 info!("Searching for album: {query}");
689 Album::search(&self.db, &query, i64::from(limit))
690 .await
691 .tap_err(|e| {
692 warn!("Error in search_album: {e}");
693 })
694 .unwrap_or_default()
695 .into()
696 }
697 #[instrument]
699 async fn search_song(self, context: Context, query: String, limit: u32) -> Box<[Song]> {
700 info!("Searching for song: {query}");
701 Song::search(&self.db, &query, i64::from(limit))
702 .await
703 .tap_err(|e| {
704 warn!("Error in search_song: {e}");
705 })
706 .unwrap_or_default()
707 .into()
708 }
709
710 #[instrument]
712 async fn playback_toggle(self, context: Context) {
713 info!("Toggling playback");
714 self.audio_kernel.send(AudioCommand::TogglePlayback);
715 }
716 #[instrument]
718 async fn playback_play(self, context: Context) {
719 info!("Starting playback");
720 self.audio_kernel.send(AudioCommand::Play);
721 }
722 #[instrument]
724 async fn playback_pause(self, context: Context) {
725 info!("Pausing playback");
726 self.audio_kernel.send(AudioCommand::Pause);
727 }
728 #[instrument]
730 async fn playback_stop(self, context: Context) {
731 info!("Stopping playback");
732 self.audio_kernel.send(AudioCommand::Stop);
733 }
734 #[instrument]
736 async fn playback_restart(self, context: Context) {
737 info!("Restarting current song");
738 self.audio_kernel.send(AudioCommand::RestartSong);
739 }
740 #[instrument]
742 async fn playback_skip_forward(self, context: Context, amount: usize) {
743 info!("Skipping forward by {amount} songs");
744 self.audio_kernel
745 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
746 }
747 #[instrument]
749 async fn playback_skip_backward(self, context: Context, amount: usize) {
750 info!("Going back by {amount} songs");
751 self.audio_kernel
752 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
753 }
754 #[instrument]
757 async fn playback_clear_player(self, context: Context) {
758 info!("Stopping playback");
759 self.audio_kernel.send(AudioCommand::ClearPlayer);
760 }
761 #[instrument]
763 async fn playback_clear(self, context: Context) {
764 info!("Clearing queue and stopping playback");
765 self.audio_kernel
766 .send(AudioCommand::Queue(QueueCommand::Clear));
767 }
768 #[instrument]
770 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
771 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
772 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
773 }
774 #[instrument]
776 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
777 info!("Setting repeat mode to: {mode}");
778 self.audio_kernel
779 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
780 }
781 #[instrument]
783 async fn playback_shuffle(self, context: Context) {
784 info!("Shuffling queue");
785 self.audio_kernel
786 .send(AudioCommand::Queue(QueueCommand::Shuffle));
787 }
788 #[instrument]
791 async fn playback_volume(self, context: Context, volume: f32) {
792 info!("Setting volume to: {volume}",);
793 self.audio_kernel
794 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
795 }
796 #[instrument]
798 async fn playback_volume_up(self, context: Context, amount: f32) {
799 info!("Increasing volume by: {amount}",);
800 self.audio_kernel
801 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
802 }
803 #[instrument]
805 async fn playback_volume_down(self, context: Context, amount: f32) {
806 info!("Decreasing volume by: {amount}",);
807 self.audio_kernel
808 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
809 }
810 #[instrument]
812 async fn playback_volume_toggle_mute(self, context: Context) {
813 info!("Toggling volume mute");
814 self.audio_kernel
815 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
816 }
817 #[instrument]
819 async fn playback_mute(self, context: Context) {
820 info!("Muting volume");
821 self.audio_kernel
822 .send(AudioCommand::Volume(VolumeCommand::Mute));
823 }
824 #[instrument]
826 async fn playback_unmute(self, context: Context) {
827 info!("Unmuting volume");
828 self.audio_kernel
829 .send(AudioCommand::Volume(VolumeCommand::Unmute));
830 }
831
832 #[instrument]
835 async fn queue_add(
836 self,
837 context: Context,
838 thing: schemas::RecordId,
839 ) -> Result<(), SerializableLibraryError> {
840 info!("Adding thing to queue: {thing}");
841
842 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
843
844 if songs.is_empty() {
845 return Err(Error::NotFound.into());
846 }
847
848 self.audio_kernel
849 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
850 songs,
851 ))));
852
853 Ok(())
854 }
855 #[instrument]
858 async fn queue_add_list(
859 self,
860 context: Context,
861 list: Vec<schemas::RecordId>,
862 ) -> Result<(), SerializableLibraryError> {
863 info!(
864 "Adding list to queue: ({})",
865 list.iter()
866 .map(ToString::to_string)
867 .collect::<Vec<_>>()
868 .join(", ")
869 );
870
871 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
873
874 self.audio_kernel
875 .send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
876 songs,
877 ))));
878
879 Ok(())
880 }
881 #[instrument]
884 async fn queue_set_index(self, context: Context, index: usize) {
885 info!("Setting queue index to: {index}");
886
887 self.audio_kernel
888 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
889 }
890 #[instrument]
893 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
894 info!("Removing queue range: {range:?}");
895
896 self.audio_kernel
897 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
898 }
899
900 #[instrument]
902 async fn playlist_list(self, context: Context) -> Box<[PlaylistBrief]> {
903 info!("Listing playlists");
904 Playlist::read_all(&self.db)
905 .await
906 .tap_err(|e| warn!("Error in playlist_list: {e}"))
907 .ok()
908 .map(|playlists| playlists.iter().map(std::convert::Into::into).collect())
909 .unwrap_or_default()
910 }
911 #[instrument]
914 async fn playlist_get_or_create(
915 self,
916 context: Context,
917 name: String,
918 ) -> Result<PlaylistId, SerializableLibraryError> {
919 info!("Creating new playlist: {name}");
920
921 match Playlist::read_by_name(&self.db, name.clone()).await {
923 Ok(Some(playlist)) => return Ok(playlist.id.into()),
924 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
925 _ => {}
926 }
927 match Playlist::create(
929 &self.db,
930 Playlist {
931 id: Playlist::generate_id(),
932 name,
933 runtime: Duration::from_secs(0),
934 song_count: 0,
935 },
936 )
937 .await
938 .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
939 {
940 Some(playlist) => Ok(playlist.id.into()),
941 None => Err(Error::NotCreated.into()),
942 }
943 }
944 #[instrument]
946 async fn playlist_remove(
947 self,
948 context: Context,
949 id: PlaylistId,
950 ) -> Result<(), SerializableLibraryError> {
951 let id = id.into();
952 info!("Removing playlist with id: {id}");
953
954 Playlist::delete(&self.db, id)
955 .await?
956 .ok_or(Error::NotFound)?;
957
958 Ok(())
959 }
960 #[instrument]
964 async fn playlist_clone(
965 self,
966 context: Context,
967 id: PlaylistId,
968 ) -> Result<PlaylistId, SerializableLibraryError> {
969 let id = id.into();
970 info!("Cloning playlist with id: {id}");
971
972 let new_playlist = Playlist::create_copy(&self.db, id)
973 .await?
974 .ok_or(Error::NotFound)?;
975
976 Ok(new_playlist.id.into())
977 }
978 #[instrument]
981 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
982 info!("Getting playlist ID: {name}");
983
984 Playlist::read_by_name(&self.db, name)
985 .await
986 .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
987 .ok()
988 .flatten()
989 .map(|playlist| playlist.id.into())
990 }
991 #[instrument]
994 async fn playlist_remove_songs(
995 self,
996 context: Context,
997 playlist: PlaylistId,
998 songs: Vec<SongId>,
999 ) -> Result<(), SerializableLibraryError> {
1000 let playlist = playlist.into();
1001 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
1002 info!("Removing song from playlist: {playlist} ({songs:?})");
1003
1004 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
1005 }
1006 #[instrument]
1009 async fn playlist_add(
1010 self,
1011 context: Context,
1012 playlist: PlaylistId,
1013 thing: schemas::RecordId,
1014 ) -> Result<(), SerializableLibraryError> {
1015 let playlist = playlist.into();
1016 info!("Adding thing to playlist: {playlist} ({thing})");
1017
1018 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1020
1021 Ok(Playlist::add_songs(
1022 &self.db,
1023 playlist,
1024 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1025 )
1026 .await?)
1027 }
1028 #[instrument]
1031 async fn playlist_add_list(
1032 self,
1033 context: Context,
1034 playlist: PlaylistId,
1035 list: Vec<schemas::RecordId>,
1036 ) -> Result<(), SerializableLibraryError> {
1037 let playlist = playlist.into();
1038 info!(
1039 "Adding list to playlist: {playlist} ({})",
1040 list.iter()
1041 .map(ToString::to_string)
1042 .collect::<Vec<_>>()
1043 .join(", ")
1044 );
1045
1046 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1048
1049 Ok(Playlist::add_songs(
1050 &self.db,
1051 playlist,
1052 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1053 )
1054 .await?)
1055 }
1056 #[instrument]
1058 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1059 let id = id.into();
1060 info!("Getting playlist by ID: {id}");
1061
1062 Playlist::read(&self.db, id)
1063 .await
1064 .tap_err(|e| warn!("Error in playlist_get: {e}"))
1065 .ok()
1066 .flatten()
1067 }
1068 #[instrument]
1070 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1071 let id = id.into();
1072 info!("Getting songs in: {id}");
1073 Playlist::read_songs(&self.db, id)
1074 .await
1075 .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1076 .ok()
1077 .map(Into::into)
1078 }
1079 #[instrument]
1081 async fn playlist_rename(
1082 self,
1083 context: Context,
1084 id: PlaylistId,
1085 name: String,
1086 ) -> Result<Playlist, SerializableLibraryError> {
1087 let id = id.into();
1088 info!("Renaming playlist: {id} ({name})");
1089 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1090 .await?
1091 .ok_or(Error::NotFound.into())
1092 }
1093 #[instrument]
1095 async fn playlist_export(
1096 self,
1097 context: Context,
1098 id: PlaylistId,
1099 path: PathBuf,
1100 ) -> Result<(), SerializableLibraryError> {
1101 validate_file_path(&path, "m3u", false)?;
1103
1104 let playlist = Playlist::read(&self.db, id.into())
1106 .await
1107 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1108 .ok()
1109 .flatten()
1110 .ok_or(Error::NotFound)?;
1111 let songs = Playlist::read_songs(&self.db, playlist.id)
1113 .await
1114 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1115 .ok()
1116 .unwrap_or_default();
1117
1118 let file = File::create(&path).tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1120 export_playlist(&playlist.name, &songs, file)
1122 .tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1123 info!("Exported playlist to: {path:?}");
1124 Ok(())
1125 }
1126 #[instrument]
1128 async fn playlist_import(
1129 self,
1130 context: Context,
1131 path: PathBuf,
1132 name: Option<String>,
1133 ) -> Result<PlaylistId, SerializableLibraryError> {
1134 validate_file_path(&path, "m3u", true)?;
1136
1137 let file = File::open(&path).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1139 let (parsed_name, song_paths) =
1140 import_playlist(file).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1141
1142 let name = match (name, parsed_name) {
1143 (Some(name), _) | (None, Some(name)) => name,
1144 (None, None) => "Imported Playlist".to_owned(),
1145 };
1146
1147 if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1149 info!("Playlist \"{name}\" already exists, will not import");
1151 return Ok(playlist.id.into());
1152 }
1153
1154 let playlist = Playlist::create(
1156 &self.db,
1157 Playlist {
1158 id: Playlist::generate_id(),
1159 name,
1160 runtime: Duration::from_secs(0),
1161 song_count: 0,
1162 },
1163 )
1164 .await
1165 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1166 .ok_or(Error::NotCreated)?;
1167
1168 let mut songs = Vec::new();
1170 for path in &song_paths {
1171 let Some(song) = Song::read_by_path(&self.db, path.clone())
1172 .await
1173 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1174 else {
1175 warn!("Song at {} not found in the library", path.display());
1176 continue;
1177 };
1178
1179 songs.push(song.id);
1180 }
1181
1182 if songs.is_empty() {
1183 return Err(BackupError::NoValidSongs(song_paths.len()).into());
1184 }
1185
1186 Playlist::add_songs(&self.db, playlist.id.clone(), songs)
1188 .await
1189 .tap_err(|e| {
1190 warn!("Error in playlist_import: {e}");
1191 })?;
1192
1193 Ok(playlist.id.into())
1195 }
1196
1197 #[instrument]
1199 async fn collection_list(self, context: Context) -> Box<[CollectionBrief]> {
1200 info!("Listing collections");
1201 Collection::read_all(&self.db)
1202 .await
1203 .tap_err(|e| warn!("Error in collection_list: {e}"))
1204 .ok()
1205 .map(|collections| collections.iter().map(std::convert::Into::into).collect())
1206 .unwrap_or_default()
1207 }
1208 #[instrument]
1210 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1211 info!("Getting collection by ID: {id:?}");
1212 Collection::read(&self.db, id.into())
1213 .await
1214 .tap_err(|e| warn!("Error in collection_get: {e}"))
1215 .ok()
1216 .flatten()
1217 }
1218 #[instrument]
1220 async fn collection_freeze(
1221 self,
1222 context: Context,
1223 id: CollectionId,
1224 name: String,
1225 ) -> Result<PlaylistId, SerializableLibraryError> {
1226 info!("Freezing collection: {id:?} ({name})");
1227 Ok(Collection::freeze(&self.db, id.into(), name)
1228 .await
1229 .map(|p| p.id.into())?)
1230 }
1231 #[instrument]
1233 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1234 let id = id.into();
1235 info!("Getting songs in: {id}");
1236 Collection::read_songs(&self.db, id)
1237 .await
1238 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1239 .ok()
1240 .map(Into::into)
1241 }
1242
1243 #[instrument]
1245 async fn radio_get_similar(
1246 self,
1247 context: Context,
1248 things: Vec<schemas::RecordId>,
1249 n: u32,
1250 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1251 #[cfg(not(feature = "analysis"))]
1252 {
1253 warn!("Analysis is not enabled");
1254 return Err(SerializableLibraryError::AnalysisNotEnabled);
1255 }
1256
1257 #[cfg(feature = "analysis")]
1258 {
1259 info!("Getting the {n} most similar songs to: {things:?}");
1260 Ok(services::radio::get_similar(&self.db, things, n)
1261 .await
1262 .map(Vec::into_boxed_slice)
1263 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1264 }
1265 }
1266 #[instrument]
1268 async fn radio_get_similar_ids(
1269 self,
1270 context: Context,
1271 things: Vec<schemas::RecordId>,
1272 n: u32,
1273 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1274 #[cfg(not(feature = "analysis"))]
1275 {
1276 warn!("Analysis is not enabled");
1277 return Err(SerializableLibraryError::AnalysisNotEnabled);
1278 }
1279
1280 #[cfg(feature = "analysis")]
1281 {
1282 info!("Getting the {n} most similar songs to: {things:?}");
1283 Ok(services::radio::get_similar(&self.db, things, n)
1284 .await
1285 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1286 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1287 }
1288 }
1289
1290 #[instrument]
1293 async fn dynamic_playlist_create(
1294 self,
1295 context: Context,
1296 name: String,
1297 query: Query,
1298 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1299 let id = DynamicPlaylist::generate_id();
1300 info!("Creating new DP: {id:?} ({name})");
1301
1302 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1303 .await
1304 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1305 {
1306 Some(dp) => Ok(dp.id.into()),
1307 None => Err(Error::NotCreated.into()),
1308 }
1309 }
1310 #[instrument]
1312 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1313 info!("Listing DPs");
1314 DynamicPlaylist::read_all(&self.db)
1315 .await
1316 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1317 .ok()
1318 .map(Into::into)
1319 .unwrap_or_default()
1320 }
1321 #[instrument]
1323 async fn dynamic_playlist_update(
1324 self,
1325 context: Context,
1326 id: DynamicPlaylistId,
1327 changes: DynamicPlaylistChangeSet,
1328 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1329 info!("Updating DP: {id:?}, {changes:?}");
1330 DynamicPlaylist::update(&self.db, id.into(), changes)
1331 .await
1332 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1333 .ok_or(Error::NotFound.into())
1334 }
1335 #[instrument]
1337 async fn dynamic_playlist_remove(
1338 self,
1339 context: Context,
1340 id: DynamicPlaylistId,
1341 ) -> Result<(), SerializableLibraryError> {
1342 info!("Removing DP with id: {id:?}");
1343 DynamicPlaylist::delete(&self.db, id.into())
1344 .await?
1345 .ok_or(Error::NotFound)?;
1346 Ok(())
1347 }
1348 #[instrument]
1350 async fn dynamic_playlist_get(
1351 self,
1352 context: Context,
1353 id: DynamicPlaylistId,
1354 ) -> Option<DynamicPlaylist> {
1355 info!("Getting DP by ID: {id:?}");
1356 DynamicPlaylist::read(&self.db, id.into())
1357 .await
1358 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1359 .ok()
1360 .flatten()
1361 }
1362 #[instrument]
1364 async fn dynamic_playlist_get_songs(
1365 self,
1366 context: Context,
1367 id: DynamicPlaylistId,
1368 ) -> Option<Box<[Song]>> {
1369 info!("Getting songs in DP: {id:?}");
1370 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1371 .await
1372 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1373 .ok()
1374 .flatten()
1375 .map(Into::into)
1376 }
1377 #[instrument]
1379 async fn dynamic_playlist_export(
1380 self,
1381 context: Context,
1382 path: PathBuf,
1383 ) -> Result<(), SerializableLibraryError> {
1384 validate_file_path(&path, "csv", false)?;
1386
1387 let playlists = DynamicPlaylist::read_all(&self.db)
1389 .await
1390 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1391
1392 let file =
1394 File::create(&path).tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1395 let writer = csv::Writer::from_writer(std::io::BufWriter::new(file));
1396 export_dynamic_playlists(&playlists, writer)
1398 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1399 info!("Exported dynamic playlists to: {path:?}");
1400 Ok(())
1401 }
1402 #[instrument]
1404 async fn dynamic_playlist_import(
1405 self,
1406 context: Context,
1407 path: PathBuf,
1408 ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError> {
1409 validate_file_path(&path, "csv", true)?;
1411
1412 let file = File::open(&path).tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1414 let reader = csv::ReaderBuilder::new()
1415 .has_headers(true)
1416 .from_reader(std::io::BufReader::new(file));
1417
1418 let playlists = import_dynamic_playlists(reader)
1420 .tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1421
1422 if playlists.is_empty() {
1423 return Err(BackupError::NoValidPlaylists.into());
1424 }
1425
1426 let mut ids = Vec::new();
1428 for playlist in playlists {
1429 if let Ok(Some(existing_playlist)) =
1431 DynamicPlaylist::read_by_name(&self.db, playlist.name.clone()).await
1432 {
1433 info!(
1434 "Dynamic Playlist \"{}\" already exists, will not import",
1435 existing_playlist.name
1436 );
1437 continue;
1438 }
1439
1440 ids.push(
1441 DynamicPlaylist::create(&self.db, playlist)
1442 .await
1443 .tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?
1444 .ok_or(Error::NotCreated)?,
1445 );
1446 }
1447
1448 Ok(ids)
1449 }
1450}