1use std::{fs::File, ops::Range, path::PathBuf, sync::Arc, time::Duration};
3use ::tarpc::context::Context;
5use log::{debug, error, info, warn};
6use surrealdb::{Surreal, engine::local::Db};
7use tokio::sync::Mutex;
8use tracing::{Instrument, instrument};
9use mecomp_core::{
11 audio::{
12 AudioKernelSender,
13 commands::{AudioCommand, QueueCommand, VolumeCommand},
14 },
15 config::Settings,
16 errors::{BackupError, SerializableLibraryError},
17 rpc::{
18 AlbumId, ArtistId, CollectionId, DynamicPlaylistId, MusicPlayer, PlaylistId, SearchResult,
19 SongId,
20 },
21 state::{
22 RepeatMode, SeekType, StateAudio,
23 library::{LibraryBrief, LibraryFull, LibraryHealth},
24 },
25 udp::{Event, Message, Sender},
26};
27use mecomp_storage::{
28 db::schemas::{
29 self,
30 album::{Album, AlbumBrief},
31 artist::{Artist, ArtistBrief},
32 collection::{Collection, CollectionBrief},
33 dynamic::{DynamicPlaylist, DynamicPlaylistChangeSet, query::Query},
34 playlist::{Playlist, PlaylistBrief, PlaylistChangeSet},
35 song::{Song, SongBrief},
36 },
37 errors::Error,
38};
39use one_or_many::OneOrMany;
40
41use crate::{
42 services::{
43 self,
44 backup::{
45 export_dynamic_playlists, export_playlist, import_dynamic_playlists, import_playlist,
46 validate_file_path,
47 },
48 },
49 termination::{self, Terminator},
50};
51
52#[derive(Clone, Debug)]
53pub struct MusicPlayerServer {
54 db: Arc<Surreal<Db>>,
55 settings: Arc<Settings>,
56 audio_kernel: Arc<AudioKernelSender>,
57 library_rescan_lock: Arc<Mutex<()>>,
58 library_analyze_lock: Arc<Mutex<()>>,
59 collection_recluster_lock: Arc<Mutex<()>>,
60 publisher: Arc<Sender<Message>>,
61 terminator: Arc<Mutex<Terminator>>,
62 interrupt: Arc<termination::InterruptReceiver>,
63}
64
65impl MusicPlayerServer {
66 #[must_use]
67 #[inline]
68 pub fn new(
69 db: Arc<Surreal<Db>>,
70 settings: Arc<Settings>,
71 audio_kernel: Arc<AudioKernelSender>,
72 event_publisher: Arc<Sender<Message>>,
73 terminator: Terminator,
74 interrupt: termination::InterruptReceiver,
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 interrupt: Arc::new(interrupt),
86 }
87 }
88
89 #[instrument]
95 #[inline]
96 pub async fn publish(
97 &self,
98 message: impl Into<Message> + Send + Sync + std::fmt::Debug,
99 ) -> Result<(), mecomp_core::errors::UdpError> {
100 self.publisher.send(message).await
101 }
102}
103
104#[allow(clippy::missing_inline_in_public_items)]
105impl MusicPlayer for MusicPlayerServer {
106 #[instrument]
107 async fn register_listener(self, context: Context, listener_addr: std::net::SocketAddr) {
108 info!("Registering listener: {listener_addr}");
109 self.publisher.add_subscriber(listener_addr).await;
110 }
111
112 async fn ping(self, _: Context) -> String {
113 "pong".to_string()
114 }
115
116 #[instrument]
118 async fn library_rescan(self, context: Context) -> Result<(), SerializableLibraryError> {
119 info!("Rescanning library");
120
121 if self.library_rescan_lock.try_lock().is_err() {
122 warn!("Library rescan already in progress");
123 return Err(SerializableLibraryError::RescanInProgress);
124 }
125
126 let span = tracing::Span::current();
127
128 tokio::task::spawn(
129 async move {
130 let _guard = self.library_rescan_lock.lock().await;
131 match services::library::rescan(
132 &self.db,
133 &self.settings.daemon.library_paths,
134 &self.settings.daemon.artist_separator,
135 &self.settings.daemon.protected_artist_names,
136 self.settings.daemon.genre_separator.as_deref(),
137 self.settings.daemon.conflict_resolution,
138 )
139 .await
140 {
141 Ok(()) => info!("Library rescan complete"),
142 Err(e) => error!("Error in library_rescan: {e}"),
143 }
144
145 let result = self.publish(Event::LibraryRescanFinished).await;
146 if let Err(e) = result {
147 error!("Error notifying clients that library_rescan_finished: {e}");
148 }
149 }
150 .instrument(span),
151 );
152
153 Ok(())
154 }
155 #[instrument]
157 async fn library_rescan_in_progress(self, context: Context) -> bool {
158 self.library_rescan_lock.try_lock().is_err()
159 }
160 #[instrument]
162 async fn library_analyze(
163 self,
164 context: Context,
165 overwrite: bool,
166 ) -> Result<(), SerializableLibraryError> {
167 info!("Analyzing library");
168
169 if self.library_analyze_lock.try_lock().is_err() {
170 warn!("Library analysis already in progress");
171 return Err(SerializableLibraryError::AnalysisInProgress);
172 }
173 let span = tracing::Span::current();
174
175 tokio::task::spawn(
176 async move {
177 let _guard = self.library_analyze_lock.lock().await;
178 match services::library::analyze(&self.db, self.interrupt.resubscribe(), overwrite)
179 .await
180 {
181 Ok(()) => info!("Library analysis complete"),
182 Err(e) => error!("Error in library_analyze: {e}"),
183 }
184
185 let result = self.publish(Event::LibraryAnalysisFinished).await;
186 if let Err(e) = result {
187 error!("Error notifying clients that library_analysis_finished: {e}");
188 }
189 }
190 .instrument(span),
191 );
192
193 Ok(())
194 }
195 #[instrument]
197 async fn library_analyze_in_progress(self, context: Context) -> bool {
198 self.library_analyze_lock.try_lock().is_err()
199 }
200 #[instrument]
202 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
203 info!("Reclustering collections");
204
205 if self.collection_recluster_lock.try_lock().is_err() {
206 warn!("Collection reclustering already in progress");
207 return Err(SerializableLibraryError::ReclusterInProgress);
208 }
209
210 let span = tracing::Span::current();
211
212 tokio::task::spawn(
213 async move {
214 let _guard = self.collection_recluster_lock.lock().await;
215 match services::library::recluster(
216 &self.db,
217 self.settings.reclustering,
218 self.interrupt.resubscribe(),
219 )
220 .await
221 {
222 Ok(()) => info!("Collection reclustering complete"),
223 Err(e) => error!("Error in library_recluster: {e}"),
224 }
225
226 let result = self.publish(Event::LibraryReclusterFinished).await;
227 if let Err(e) = result {
228 error!("Error notifying clients that library_recluster_finished: {e}");
229 }
230 }
231 .instrument(span),
232 );
233
234 Ok(())
235 }
236 #[instrument]
238 async fn library_recluster_in_progress(self, context: Context) -> bool {
239 self.collection_recluster_lock.try_lock().is_err()
240 }
241 #[instrument]
243 async fn library_brief(
244 self,
245 context: Context,
246 ) -> Result<LibraryBrief, SerializableLibraryError> {
247 info!("Creating library brief");
248 Ok(services::library::brief(&self.db)
249 .await
250 .inspect_err(|e| warn!("Error in library_brief: {e}"))?)
251 }
252 #[instrument]
254 async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
255 info!("Creating library full");
256 Ok(services::library::full(&self.db)
257 .await
258 .inspect_err(|e| warn!("Error in library_full: {e}"))?)
259 }
260 #[instrument]
262 async fn library_artists_brief(
263 self,
264 context: Context,
265 ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
266 info!("Creating library artists brief");
267 Ok(Artist::read_all_brief(&self.db)
268 .await
269 .inspect_err(|e| warn!("Error in library_artists_brief: {e}"))?
270 .into_boxed_slice())
271 }
272 #[instrument]
274 async fn library_artists_full(
275 self,
276 context: Context,
277 ) -> Result<Box<[Artist]>, SerializableLibraryError> {
278 info!("Creating library artists full");
279 Ok(Artist::read_all(&self.db)
280 .await
281 .inspect_err(|e| warn!("Error in library_artists_brief: {e}"))?
282 .into_boxed_slice())
283 }
284 #[instrument]
286 async fn library_albums_brief(
287 self,
288 context: Context,
289 ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
290 info!("Creating library albums brief");
291 Ok(Album::read_all_brief(&self.db)
292 .await
293 .inspect_err(|e| warn!("Error in library_albums_brief: {e}"))?
294 .into_boxed_slice())
295 }
296 #[instrument]
298 async fn library_albums_full(
299 self,
300 context: Context,
301 ) -> Result<Box<[Album]>, SerializableLibraryError> {
302 info!("Creating library albums full");
303 Ok(Album::read_all(&self.db)
304 .await
305 .map(std::vec::Vec::into_boxed_slice)
306 .inspect_err(|e| warn!("Error in library_albums_full: {e}"))?)
307 }
308 #[instrument]
310 async fn library_songs_brief(
311 self,
312 context: Context,
313 ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
314 info!("Creating library songs brief");
315 Ok(Song::read_all_brief(&self.db)
316 .await
317 .inspect_err(|e| warn!("Error in library_songs_brief: {e}"))?
318 .into_boxed_slice())
319 }
320 #[instrument]
322 async fn library_songs_full(
323 self,
324 context: Context,
325 ) -> Result<Box<[Song]>, SerializableLibraryError> {
326 info!("Creating library songs full");
327 Ok(Song::read_all(&self.db)
328 .await
329 .inspect_err(|e| warn!("Error in library_songs_full: {e}"))?
330 .into_boxed_slice())
331 }
332 #[instrument]
334 async fn library_playlists_brief(
335 self,
336 context: Context,
337 ) -> Result<Box<[PlaylistBrief]>, SerializableLibraryError> {
338 info!("Creating library playlists brief");
339 Ok(Playlist::read_all_brief(&self.db)
340 .await
341 .inspect_err(|e| warn!("Error in library_playlists_brief: {e}"))?
342 .into_boxed_slice())
343 }
344 #[instrument]
346 async fn library_playlists_full(
347 self,
348 context: Context,
349 ) -> Result<Box<[Playlist]>, SerializableLibraryError> {
350 info!("Creating library playlists full");
351 Ok(Playlist::read_all(&self.db)
352 .await
353 .inspect_err(|e| warn!("Error in library_playlists_full: {e}"))?
354 .into_boxed_slice())
355 }
356 #[instrument]
358 async fn library_collections_brief(
359 self,
360 context: Context,
361 ) -> Result<Box<[CollectionBrief]>, SerializableLibraryError> {
362 info!("Creating library collections brief");
363 Ok(Collection::read_all_brief(&self.db)
364 .await
365 .inspect_err(|e| warn!("Error in library_collections_brief: {e}"))?
366 .into_boxed_slice())
367 }
368 #[instrument]
370 async fn library_collections_full(
371 self,
372 context: Context,
373 ) -> Result<Box<[Collection]>, SerializableLibraryError> {
374 info!("Creating library collections full");
375 Ok(Collection::read_all(&self.db)
376 .await
377 .inspect_err(|e| warn!("Error in library_collections_full: {e}"))?
378 .into_boxed_slice())
379 }
380 #[instrument]
382 async fn library_health(
383 self,
384 context: Context,
385 ) -> Result<LibraryHealth, SerializableLibraryError> {
386 info!("Creating library health");
387 Ok(services::library::health(&self.db)
388 .await
389 .inspect_err(|e| warn!("Error in library_health: {e}"))?)
390 }
391 #[instrument]
393 async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
394 let id = id.into();
395 info!("Getting song by ID: {id}");
396 Song::read(&self.db, id)
397 .await
398 .inspect_err(|e| warn!("Error in library_song_get: {e}"))
399 .unwrap_or_default()
400 }
401 #[instrument]
403 async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
404 info!("Getting song by path: {}", path.display());
405 Song::read_by_path(&self.db, path)
406 .await
407 .inspect_err(|e| warn!("Error in library_song_get_by_path: {e}"))
408 .unwrap_or_default()
409 }
410 #[instrument]
412 async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
413 let id = id.into();
414 info!("Getting artist of: {id}");
415 Song::read_artist(&self.db, id)
416 .await
417 .inspect_err(|e| warn!("Error in library_song_get_artist: {e}"))
418 .unwrap_or_default()
419 }
420 #[instrument]
422 async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
423 let id = id.into();
424 info!("Getting album of: {id}");
425 Song::read_album(&self.db, id)
426 .await
427 .inspect_err(|e| warn!("Error in library_song_get_album: {e}"))
428 .unwrap_or_default()
429 }
430 #[instrument]
432 async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
433 let id = id.into();
434 info!("Getting playlists of: {id}");
435 Song::read_playlists(&self.db, id)
436 .await
437 .inspect_err(|e| warn!("Error in library_song_get_playlists: {e}"))
438 .ok()
439 .unwrap_or_default()
440 .into()
441 }
442 #[instrument]
444 async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
445 let id = id.into();
446 info!("Getting collections of: {id}");
447 Song::read_collections(&self.db, id)
448 .await
449 .inspect_err(|e| warn!("Error in library_song_get_collections: {e}"))
450 .ok()
451 .unwrap_or_default()
452 .into()
453 }
454
455 #[instrument]
457 async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
458 let id = id.into();
459 info!("Getting album by ID: {id}");
460 Album::read(&self.db, id)
461 .await
462 .inspect_err(|e| warn!("Error in library_album_get: {e}"))
463 .ok()
464 .flatten()
465 }
466 #[instrument]
468 async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
469 let id = id.into();
470 info!("Getting artists of: {id}");
471 Album::read_artist(&self.db, id)
472 .await
473 .inspect_err(|e| warn!("Error in library_album_get_artist: {e}"))
474 .ok()
475 .into()
476 }
477 #[instrument]
479 async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
480 let id = id.into();
481 info!("Getting songs of: {id}");
482 Album::read_songs(&self.db, id)
483 .await
484 .inspect_err(|e| warn!("Error in library_album_get_songs: {e}"))
485 .ok()
486 .map(Vec::into_boxed_slice)
487 }
488 #[instrument]
490 async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
491 let id = id.into();
492 info!("Getting artist by ID: {id}");
493 Artist::read(&self.db, id)
494 .await
495 .inspect_err(|e| warn!("Error in library_artist_get: {e}"))
496 .ok()
497 .flatten()
498 }
499 #[instrument]
501 async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
502 let id = id.into();
503 info!("Getting songs of: {id}");
504 Artist::read_songs(&self.db, id)
505 .await
506 .inspect_err(|e| warn!("Error in library_artist_get_songs: {e}"))
507 .ok()
508 .map(Vec::into_boxed_slice)
509 }
510 #[instrument]
512 async fn library_artist_get_albums(
513 self,
514 context: Context,
515 id: ArtistId,
516 ) -> Option<Box<[Album]>> {
517 let id = id.into();
518 info!("Getting albums of: {id}");
519 Artist::read_albums(&self.db, id)
520 .await
521 .inspect_err(|e| warn!("Error in library_artist_get_albums: {e}"))
522 .ok()
523 .map(Vec::into_boxed_slice)
524 }
525
526 #[instrument]
528 async fn daemon_shutdown(self, context: Context) {
529 let terminator = self.terminator.clone();
530 std::thread::Builder::new()
531 .name(String::from("Daemon Shutdown"))
532 .spawn(move || {
533 std::thread::sleep(std::time::Duration::from_secs(1));
534 let terminate_result = terminator
535 .blocking_lock()
536 .terminate(termination::Interrupted::UserInt);
537 if let Err(e) = terminate_result {
538 error!("Error terminating daemon, panicking instead: {e}");
539 panic!("Error terminating daemon: {e}");
540 }
541 })
542 .unwrap();
543 info!("Shutting down daemon in 1 second");
544 }
545
546 #[instrument]
548 async fn state_audio(self, context: Context) -> Option<StateAudio> {
549 debug!("Getting state of audio player");
550 let (tx, rx) = tokio::sync::oneshot::channel();
551
552 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
553
554 rx.await
555 .inspect_err(|e| warn!("Error in state_audio: {e}"))
556 .ok()
557 }
558
559 #[instrument]
561 async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
562 info!("Getting current artist");
563 let (tx, rx) = tokio::sync::oneshot::channel();
564
565 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
566
567 if let Some(song) = rx
568 .await
569 .inspect_err(|e| warn!("Error in current_artist: {e}"))
570 .ok()
571 .and_then(|state| state.current_song)
572 {
573 Song::read_artist(&self.db, song.id)
574 .await
575 .inspect_err(|e| warn!("Error in current_album: {e}"))
576 .unwrap_or_default()
577 } else {
578 OneOrMany::None
579 }
580 }
581 #[instrument]
583 async fn current_album(self, context: Context) -> Option<Album> {
584 info!("Getting current album");
585 let (tx, rx) = tokio::sync::oneshot::channel();
586
587 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
588
589 if let Some(song) = rx
590 .await
591 .inspect_err(|e| warn!("Error in current_album: {e}"))
592 .ok()
593 .and_then(|state| state.current_song)
594 {
595 Song::read_album(&self.db, song.id)
596 .await
597 .inspect_err(|e| warn!("Error in current_album: {e}"))
598 .unwrap_or_default()
599 } else {
600 None
601 }
602 }
603 #[instrument]
605 async fn current_song(self, context: Context) -> Option<SongBrief> {
606 info!("Getting current song");
607 let (tx, rx) = tokio::sync::oneshot::channel();
608
609 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
610
611 rx.await
612 .inspect_err(|e| warn!("Error in current_song: {e}"))
613 .ok()
614 .and_then(|state| state.current_song)
615 }
616
617 #[instrument]
619 async fn rand_artist(self, context: Context) -> Option<ArtistBrief> {
620 info!("Getting random artist");
621 Artist::read_rand(&self.db, 1)
622 .await
623 .inspect_err(|e| warn!("Error in rand_artist: {e}"))
624 .ok()
625 .and_then(|results| results.first().cloned())
626 }
627 #[instrument]
629 async fn rand_album(self, context: Context) -> Option<AlbumBrief> {
630 info!("Getting random album");
631 Album::read_rand(&self.db, 1)
632 .await
633 .inspect_err(|e| warn!("Error in rand_album: {e}"))
634 .ok()
635 .and_then(|results| results.first().cloned())
636 }
637 #[instrument]
639 async fn rand_song(self, context: Context) -> Option<SongBrief> {
640 info!("Getting random song");
641 Song::read_rand(&self.db, 1)
642 .await
643 .inspect_err(|e| warn!("Error in rand_song: {e}"))
644 .ok()
645 .and_then(|results| results.first().cloned())
646 }
647
648 #[instrument]
650 async fn search(self, context: Context, query: String, limit: usize) -> SearchResult {
651 info!("Searching for: {query}");
652 let songs = Song::search(&self.db, &query, limit)
658 .await
659 .inspect_err(|e| warn!("Error in search: {e}"))
660 .unwrap_or_default()
661 .into();
662
663 let albums = Album::search(&self.db, &query, limit)
664 .await
665 .inspect_err(|e| warn!("Error in search: {e}"))
666 .unwrap_or_default()
667 .into();
668
669 let artists = Artist::search(&self.db, &query, limit)
670 .await
671 .inspect_err(|e| warn!("Error in search: {e}"))
672 .unwrap_or_default()
673 .into();
674 SearchResult {
675 songs,
676 albums,
677 artists,
678 }
679 }
680 #[instrument]
682 async fn search_artist(
683 self,
684 context: Context,
685 query: String,
686 limit: usize,
687 ) -> Box<[ArtistBrief]> {
688 info!("Searching for artist: {query}");
689 Artist::search(&self.db, &query, limit)
690 .await
691 .inspect_err(|e| {
692 warn!("Error in search_artist: {e}");
693 })
694 .unwrap_or_default()
695 .into()
696 }
697 #[instrument]
699 async fn search_album(
700 self,
701 context: Context,
702 query: String,
703 limit: usize,
704 ) -> Box<[AlbumBrief]> {
705 info!("Searching for album: {query}");
706 Album::search(&self.db, &query, limit)
707 .await
708 .inspect_err(|e| {
709 warn!("Error in search_album: {e}");
710 })
711 .unwrap_or_default()
712 .into()
713 }
714 #[instrument]
716 async fn search_song(self, context: Context, query: String, limit: usize) -> Box<[SongBrief]> {
717 info!("Searching for song: {query}");
718 Song::search(&self.db, &query, limit)
719 .await
720 .inspect_err(|e| {
721 warn!("Error in search_song: {e}");
722 })
723 .unwrap_or_default()
724 .into()
725 }
726
727 #[instrument]
729 async fn playback_toggle(self, context: Context) {
730 info!("Toggling playback");
731 self.audio_kernel.send(AudioCommand::TogglePlayback);
732 }
733 #[instrument]
735 async fn playback_play(self, context: Context) {
736 info!("Starting playback");
737 self.audio_kernel.send(AudioCommand::Play);
738 }
739 #[instrument]
741 async fn playback_pause(self, context: Context) {
742 info!("Pausing playback");
743 self.audio_kernel.send(AudioCommand::Pause);
744 }
745 #[instrument]
747 async fn playback_stop(self, context: Context) {
748 info!("Stopping playback");
749 self.audio_kernel.send(AudioCommand::Stop);
750 }
751 #[instrument]
753 async fn playback_restart(self, context: Context) {
754 info!("Restarting current song");
755 self.audio_kernel.send(AudioCommand::RestartSong);
756 }
757 #[instrument]
759 async fn playback_skip_forward(self, context: Context, amount: usize) {
760 info!("Skipping forward by {amount} songs");
761 self.audio_kernel
762 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
763 }
764 #[instrument]
766 async fn playback_skip_backward(self, context: Context, amount: usize) {
767 info!("Going back by {amount} songs");
768 self.audio_kernel
769 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
770 }
771 #[instrument]
774 async fn playback_clear_player(self, context: Context) {
775 info!("Stopping playback");
776 self.audio_kernel.send(AudioCommand::ClearPlayer);
777 }
778 #[instrument]
780 async fn playback_clear(self, context: Context) {
781 info!("Clearing queue and stopping playback");
782 self.audio_kernel
783 .send(AudioCommand::Queue(QueueCommand::Clear));
784 }
785 #[instrument]
787 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
788 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
789 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
790 }
791 #[instrument]
793 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
794 info!("Setting repeat mode to: {mode}");
795 self.audio_kernel
796 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
797 }
798 #[instrument]
800 async fn playback_shuffle(self, context: Context) {
801 info!("Shuffling queue");
802 self.audio_kernel
803 .send(AudioCommand::Queue(QueueCommand::Shuffle));
804 }
805 #[instrument]
808 async fn playback_volume(self, context: Context, volume: f32) {
809 info!("Setting volume to: {volume}",);
810 self.audio_kernel
811 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
812 }
813 #[instrument]
815 async fn playback_volume_up(self, context: Context, amount: f32) {
816 info!("Increasing volume by: {amount}",);
817 self.audio_kernel
818 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
819 }
820 #[instrument]
822 async fn playback_volume_down(self, context: Context, amount: f32) {
823 info!("Decreasing volume by: {amount}",);
824 self.audio_kernel
825 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
826 }
827 #[instrument]
829 async fn playback_volume_toggle_mute(self, context: Context) {
830 info!("Toggling volume mute");
831 self.audio_kernel
832 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
833 }
834 #[instrument]
836 async fn playback_mute(self, context: Context) {
837 info!("Muting volume");
838 self.audio_kernel
839 .send(AudioCommand::Volume(VolumeCommand::Mute));
840 }
841 #[instrument]
843 async fn playback_unmute(self, context: Context) {
844 info!("Unmuting volume");
845 self.audio_kernel
846 .send(AudioCommand::Volume(VolumeCommand::Unmute));
847 }
848
849 #[instrument]
852 async fn queue_add(
853 self,
854 context: Context,
855 thing: schemas::RecordId,
856 ) -> Result<(), SerializableLibraryError> {
857 info!("Adding thing to queue: {thing}");
858
859 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
860
861 if songs.is_empty() {
862 return Err(Error::NotFound.into());
863 }
864
865 self.audio_kernel
866 .send(AudioCommand::Queue(QueueCommand::AddToQueue(
867 songs.into_iter().map(Into::into).collect(),
868 )));
869
870 Ok(())
871 }
872 #[instrument]
875 async fn queue_add_list(
876 self,
877 context: Context,
878 list: Vec<schemas::RecordId>,
879 ) -> Result<(), SerializableLibraryError> {
880 info!(
881 "Adding list to queue: ({})",
882 list.iter()
883 .map(ToString::to_string)
884 .collect::<Vec<_>>()
885 .join(", ")
886 );
887
888 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
890
891 self.audio_kernel
892 .send(AudioCommand::Queue(QueueCommand::AddToQueue(
893 songs.into_iter().map(Into::into).collect(),
894 )));
895
896 Ok(())
897 }
898 #[instrument]
901 async fn queue_set_index(self, context: Context, index: usize) {
902 info!("Setting queue index to: {index}");
903
904 self.audio_kernel
905 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
906 }
907 #[instrument]
910 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
911 info!("Removing queue range: {range:?}");
912
913 self.audio_kernel
914 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
915 }
916
917 #[instrument]
920 async fn playlist_get_or_create(
921 self,
922 context: Context,
923 name: String,
924 ) -> Result<PlaylistId, SerializableLibraryError> {
925 info!("Creating new playlist: {name}");
926
927 match Playlist::read_by_name(&self.db, name.clone()).await {
929 Ok(Some(playlist)) => return Ok(playlist.id.into()),
930 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
931 _ => {}
932 }
933 match Playlist::create(
935 &self.db,
936 Playlist {
937 id: Playlist::generate_id(),
938 name,
939 runtime: Duration::from_secs(0),
940 song_count: 0,
941 },
942 )
943 .await
944 .inspect_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
945 {
946 Some(playlist) => Ok(playlist.id.into()),
947 None => Err(Error::NotCreated.into()),
948 }
949 }
950 #[instrument]
952 async fn playlist_remove(
953 self,
954 context: Context,
955 id: PlaylistId,
956 ) -> Result<(), SerializableLibraryError> {
957 let id = id.into();
958 info!("Removing playlist with id: {id}");
959
960 Playlist::delete(&self.db, id)
961 .await?
962 .ok_or(Error::NotFound)?;
963
964 Ok(())
965 }
966 #[instrument]
970 async fn playlist_clone(
971 self,
972 context: Context,
973 id: PlaylistId,
974 ) -> Result<PlaylistId, SerializableLibraryError> {
975 let id = id.into();
976 info!("Cloning playlist with id: {id}");
977
978 let new_playlist = Playlist::create_copy(&self.db, id)
979 .await?
980 .ok_or(Error::NotFound)?;
981
982 Ok(new_playlist.id.into())
983 }
984 #[instrument]
987 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
988 info!("Getting playlist ID: {name}");
989
990 Playlist::read_by_name(&self.db, name)
991 .await
992 .inspect_err(|e| warn!("Error in playlist_get_id: {e}"))
993 .ok()
994 .flatten()
995 .map(|playlist| playlist.id.into())
996 }
997 #[instrument]
1000 async fn playlist_remove_songs(
1001 self,
1002 context: Context,
1003 playlist: PlaylistId,
1004 songs: Vec<SongId>,
1005 ) -> Result<(), SerializableLibraryError> {
1006 let playlist = playlist.into();
1007 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
1008 info!("Removing song from playlist: {playlist} ({songs:?})");
1009
1010 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
1011 }
1012 #[instrument]
1015 async fn playlist_add(
1016 self,
1017 context: Context,
1018 playlist: PlaylistId,
1019 thing: schemas::RecordId,
1020 ) -> Result<(), SerializableLibraryError> {
1021 let playlist = playlist.into();
1022 info!("Adding thing to playlist: {playlist} ({thing})");
1023
1024 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1026
1027 Ok(Playlist::add_songs(
1028 &self.db,
1029 playlist,
1030 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1031 )
1032 .await?)
1033 }
1034 #[instrument]
1037 async fn playlist_add_list(
1038 self,
1039 context: Context,
1040 playlist: PlaylistId,
1041 list: Vec<schemas::RecordId>,
1042 ) -> Result<(), SerializableLibraryError> {
1043 let playlist = playlist.into();
1044 info!(
1045 "Adding list to playlist: {playlist} ({})",
1046 list.iter()
1047 .map(ToString::to_string)
1048 .collect::<Vec<_>>()
1049 .join(", ")
1050 );
1051
1052 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1054
1055 Ok(Playlist::add_songs(
1056 &self.db,
1057 playlist,
1058 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1059 )
1060 .await?)
1061 }
1062 #[instrument]
1064 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1065 let id = id.into();
1066 info!("Getting playlist by ID: {id}");
1067
1068 Playlist::read(&self.db, id)
1069 .await
1070 .inspect_err(|e| warn!("Error in playlist_get: {e}"))
1071 .ok()
1072 .flatten()
1073 }
1074 #[instrument]
1076 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1077 let id = id.into();
1078 info!("Getting songs in: {id}");
1079 Playlist::read_songs(&self.db, id)
1080 .await
1081 .inspect_err(|e| warn!("Error in playlist_get_songs: {e}"))
1082 .ok()
1083 .map(Into::into)
1084 }
1085 #[instrument]
1087 async fn playlist_rename(
1088 self,
1089 context: Context,
1090 id: PlaylistId,
1091 name: String,
1092 ) -> Result<Playlist, SerializableLibraryError> {
1093 let id = id.into();
1094 info!("Renaming playlist: {id} ({name})");
1095 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1096 .await?
1097 .ok_or(Error::NotFound.into())
1098 }
1099 #[instrument]
1101 async fn playlist_export(
1102 self,
1103 context: Context,
1104 id: PlaylistId,
1105 path: PathBuf,
1106 ) -> Result<(), SerializableLibraryError> {
1107 info!("Exporting playlist to: {}", path.display());
1108
1109 validate_file_path(&path, "m3u", false)?;
1111
1112 let playlist = Playlist::read(&self.db, id.into())
1114 .await
1115 .inspect_err(|e| warn!("Error in playlist_export: {e}"))
1116 .ok()
1117 .flatten()
1118 .ok_or(Error::NotFound)?;
1119 let songs = Playlist::read_songs(&self.db, playlist.id)
1121 .await
1122 .inspect_err(|e| warn!("Error in playlist_export: {e}"))
1123 .ok()
1124 .unwrap_or_default();
1125
1126 let file = File::create(&path).inspect_err(|e| warn!("Error in playlist_export: {e}"))?;
1128 export_playlist(&playlist.name, &songs, file)
1130 .inspect_err(|e| warn!("Error in playlist_export: {e}"))?;
1131 info!("Exported playlist to: {}", path.display());
1132 Ok(())
1133 }
1134 #[instrument]
1136 async fn playlist_import(
1137 self,
1138 context: Context,
1139 path: PathBuf,
1140 name: Option<String>,
1141 ) -> Result<PlaylistId, SerializableLibraryError> {
1142 info!("Importing playlist from: {}", path.display());
1143
1144 validate_file_path(&path, "m3u", true)?;
1146
1147 let file = File::open(&path).inspect_err(|e| warn!("Error in playlist_import: {e}"))?;
1149 let (parsed_name, song_paths) =
1150 import_playlist(file).inspect_err(|e| warn!("Error in playlist_import: {e}"))?;
1151
1152 log::debug!("Parsed playlist name: {parsed_name:?}");
1153 log::debug!("Parsed song paths: {song_paths:?}");
1154
1155 let name = match (name, parsed_name) {
1156 (Some(name), _) | (None, Some(name)) => name,
1157 (None, None) => "Imported Playlist".to_owned(),
1158 };
1159
1160 if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1162 info!("Playlist \"{name}\" already exists, will not import");
1164 return Ok(playlist.id.into());
1165 }
1166
1167 let playlist = Playlist::create(
1169 &self.db,
1170 Playlist {
1171 id: Playlist::generate_id(),
1172 name,
1173 runtime: Duration::from_secs(0),
1174 song_count: 0,
1175 },
1176 )
1177 .await
1178 .inspect_err(|e| warn!("Error in playlist_import: {e}"))?
1179 .ok_or(Error::NotCreated)?;
1180
1181 let mut songs = Vec::new();
1183 for path in &song_paths {
1184 let Some(song) = Song::read_by_path(&self.db, path.clone())
1185 .await
1186 .inspect_err(|e| warn!("Error in playlist_import: {e}"))?
1187 else {
1188 warn!("Song at {} not found in the library", path.display());
1189 continue;
1190 };
1191
1192 songs.push(song.id);
1193 }
1194
1195 if songs.is_empty() {
1196 return Err(BackupError::NoValidSongs(song_paths.len()).into());
1197 }
1198
1199 Playlist::add_songs(&self.db, playlist.id.clone(), songs)
1201 .await
1202 .inspect_err(|e| {
1203 warn!("Error in playlist_import: {e}");
1204 })?;
1205
1206 Ok(playlist.id.into())
1208 }
1209
1210 #[instrument]
1212 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1213 info!("Getting collection by ID: {id:?}");
1214 Collection::read(&self.db, id.into())
1215 .await
1216 .inspect_err(|e| warn!("Error in collection_get: {e}"))
1217 .ok()
1218 .flatten()
1219 }
1220 #[instrument]
1222 async fn collection_freeze(
1223 self,
1224 context: Context,
1225 id: CollectionId,
1226 name: String,
1227 ) -> Result<PlaylistId, SerializableLibraryError> {
1228 info!("Freezing collection: {id:?} ({name})");
1229 Ok(Collection::freeze(&self.db, id.into(), name)
1230 .await
1231 .map(|p| p.id.into())?)
1232 }
1233 #[instrument]
1235 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1236 let id = id.into();
1237 info!("Getting songs in: {id}");
1238 Collection::read_songs(&self.db, id)
1239 .await
1240 .inspect_err(|e| warn!("Error in collection_get_songs: {e}"))
1241 .ok()
1242 .map(Into::into)
1243 }
1244
1245 #[instrument]
1247 async fn radio_get_similar(
1248 self,
1249 context: Context,
1250 things: Vec<schemas::RecordId>,
1251 n: u32,
1252 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1253 info!("Getting the {n} most similar songs to: {things:?}");
1254 Ok(services::radio::get_similar(&self.db, things, n)
1255 .await
1256 .map(Vec::into_boxed_slice)
1257 .inspect_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1258 }
1259 #[instrument]
1261 async fn radio_get_similar_ids(
1262 self,
1263 context: Context,
1264 things: Vec<schemas::RecordId>,
1265 n: u32,
1266 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1267 info!("Getting the {n} most similar songs to: {things:?}");
1268 Ok(services::radio::get_similar(&self.db, things, n)
1269 .await
1270 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1271 .inspect_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1272 }
1273
1274 #[instrument]
1277 async fn dynamic_playlist_create(
1278 self,
1279 context: Context,
1280 name: String,
1281 query: Query,
1282 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1283 let id = DynamicPlaylist::generate_id();
1284 info!("Creating new DP: {id:?} ({name})");
1285
1286 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1287 .await
1288 .inspect_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1289 {
1290 Some(dp) => Ok(dp.id.into()),
1291 None => Err(Error::NotCreated.into()),
1292 }
1293 }
1294 #[instrument]
1296 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1297 info!("Listing DPs");
1298 DynamicPlaylist::read_all(&self.db)
1299 .await
1300 .inspect_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1301 .ok()
1302 .map(Into::into)
1303 .unwrap_or_default()
1304 }
1305 #[instrument]
1307 async fn dynamic_playlist_update(
1308 self,
1309 context: Context,
1310 id: DynamicPlaylistId,
1311 changes: DynamicPlaylistChangeSet,
1312 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1313 info!("Updating DP: {id:?}, {changes:?}");
1314 DynamicPlaylist::update(&self.db, id.into(), changes)
1315 .await
1316 .inspect_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1317 .ok_or(Error::NotFound.into())
1318 }
1319 #[instrument]
1321 async fn dynamic_playlist_remove(
1322 self,
1323 context: Context,
1324 id: DynamicPlaylistId,
1325 ) -> Result<(), SerializableLibraryError> {
1326 info!("Removing DP with id: {id:?}");
1327 DynamicPlaylist::delete(&self.db, id.into())
1328 .await?
1329 .ok_or(Error::NotFound)?;
1330 Ok(())
1331 }
1332 #[instrument]
1334 async fn dynamic_playlist_get(
1335 self,
1336 context: Context,
1337 id: DynamicPlaylistId,
1338 ) -> Option<DynamicPlaylist> {
1339 info!("Getting DP by ID: {id:?}");
1340 DynamicPlaylist::read(&self.db, id.into())
1341 .await
1342 .inspect_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1343 .ok()
1344 .flatten()
1345 }
1346 #[instrument]
1348 async fn dynamic_playlist_get_songs(
1349 self,
1350 context: Context,
1351 id: DynamicPlaylistId,
1352 ) -> Option<Box<[Song]>> {
1353 info!("Getting songs in DP: {id:?}");
1354 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1355 .await
1356 .inspect_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1357 .ok()
1358 .flatten()
1359 .map(Into::into)
1360 }
1361 #[instrument]
1363 async fn dynamic_playlist_export(
1364 self,
1365 context: Context,
1366 path: PathBuf,
1367 ) -> Result<(), SerializableLibraryError> {
1368 info!("Exporting dynamic playlists to: {}", path.display());
1369
1370 validate_file_path(&path, "csv", false)?;
1372
1373 let playlists = DynamicPlaylist::read_all(&self.db)
1375 .await
1376 .inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1377
1378 let file =
1380 File::create(&path).inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1381 let writer = csv::Writer::from_writer(std::io::BufWriter::new(file));
1382 export_dynamic_playlists(&playlists, writer)
1384 .inspect_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1385 info!("Exported dynamic playlists to: {}", path.display());
1386 Ok(())
1387 }
1388 #[instrument]
1390 async fn dynamic_playlist_import(
1391 self,
1392 context: Context,
1393 path: PathBuf,
1394 ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError> {
1395 info!("Importing dynamic playlists from: {}", path.display());
1396
1397 validate_file_path(&path, "csv", true)?;
1399
1400 let file =
1402 File::open(&path).inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1403 let reader = csv::ReaderBuilder::new()
1404 .has_headers(true)
1405 .from_reader(std::io::BufReader::new(file));
1406
1407 let playlists = import_dynamic_playlists(reader)
1409 .inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?;
1410
1411 if playlists.is_empty() {
1412 return Err(BackupError::NoValidPlaylists.into());
1413 }
1414
1415 let mut ids = Vec::new();
1417 for playlist in playlists {
1418 if let Ok(Some(existing_playlist)) =
1420 DynamicPlaylist::read_by_name(&self.db, playlist.name.clone()).await
1421 {
1422 info!(
1423 "Dynamic Playlist \"{}\" already exists, will not import",
1424 existing_playlist.name
1425 );
1426 continue;
1427 }
1428
1429 ids.push(
1430 DynamicPlaylist::create(&self.db, playlist)
1431 .await
1432 .inspect_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?
1433 .ok_or(Error::NotCreated)?,
1434 );
1435 }
1436
1437 Ok(ids)
1438 }
1439}