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 tap::TapFallible;
8use tokio::sync::Mutex;
9use tracing::{Instrument, instrument};
10use mecomp_core::{
12 audio::{
13 AudioKernelSender,
14 commands::{AudioCommand, QueueCommand, VolumeCommand},
15 },
16 config::Settings,
17 errors::{BackupError, SerializableLibraryError},
18 rpc::{
19 AlbumId, ArtistId, CollectionId, DynamicPlaylistId, MusicPlayer, PlaylistId, SearchResult,
20 SongId,
21 },
22 state::{
23 RepeatMode, SeekType, StateAudio,
24 library::{LibraryBrief, LibraryFull, LibraryHealth},
25 },
26 udp::{Event, Message, Sender},
27};
28use mecomp_storage::{
29 db::schemas::{
30 self,
31 album::{Album, AlbumBrief},
32 artist::{Artist, ArtistBrief},
33 collection::{Collection, CollectionBrief},
34 dynamic::{DynamicPlaylist, DynamicPlaylistChangeSet, query::Query},
35 playlist::{Playlist, PlaylistBrief, PlaylistChangeSet},
36 song::{Song, SongBrief},
37 },
38 errors::Error,
39};
40use one_or_many::OneOrMany;
41
42use crate::{
43 services::{
44 self,
45 backup::{
46 export_dynamic_playlists, export_playlist, import_dynamic_playlists, import_playlist,
47 validate_file_path,
48 },
49 },
50 termination::{self, Terminator},
51};
52
53#[derive(Clone, Debug)]
54pub struct MusicPlayerServer {
55 db: Arc<Surreal<Db>>,
56 settings: Arc<Settings>,
57 audio_kernel: Arc<AudioKernelSender>,
58 library_rescan_lock: Arc<Mutex<()>>,
59 library_analyze_lock: Arc<Mutex<()>>,
60 collection_recluster_lock: Arc<Mutex<()>>,
61 publisher: Arc<Sender<Message>>,
62 terminator: Arc<Mutex<Terminator>>,
63 interrupt: Arc<termination::InterruptReceiver>,
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<Sender<Message>>,
74 terminator: Terminator,
75 interrupt: termination::InterruptReceiver,
76 ) -> Self {
77 Self {
78 db,
79 publisher: event_publisher,
80 settings,
81 audio_kernel,
82 library_rescan_lock: Arc::new(Mutex::new(())),
83 library_analyze_lock: Arc::new(Mutex::new(())),
84 collection_recluster_lock: Arc::new(Mutex::new(())),
85 terminator: Arc::new(Mutex::new(terminator)),
86 interrupt: Arc::new(interrupt),
87 }
88 }
89
90 #[instrument]
96 #[inline]
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.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.add_subscriber(listener_addr).await;
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 info!("Analyzing library");
169
170 if self.library_analyze_lock.try_lock().is_err() {
171 warn!("Library analysis already in progress");
172 return Err(SerializableLibraryError::AnalysisInProgress);
173 }
174 let span = tracing::Span::current();
175
176 tokio::task::spawn(
177 async move {
178 let _guard = self.library_analyze_lock.lock().await;
179 match services::library::analyze(&self.db, self.interrupt.resubscribe(), overwrite)
180 .await
181 {
182 Ok(()) => info!("Library analysis complete"),
183 Err(e) => error!("Error in library_analyze: {e}"),
184 }
185
186 let result = self.publish(Event::LibraryAnalysisFinished).await;
187 if let Err(e) = result {
188 error!("Error notifying clients that library_analysis_finished: {e}");
189 }
190 }
191 .instrument(span),
192 );
193
194 Ok(())
195 }
196 #[instrument]
198 async fn library_analyze_in_progress(self, context: Context) -> bool {
199 self.library_analyze_lock.try_lock().is_err()
200 }
201 #[instrument]
203 async fn library_recluster(self, context: Context) -> Result<(), SerializableLibraryError> {
204 info!("Reclustering collections");
205
206 if self.collection_recluster_lock.try_lock().is_err() {
207 warn!("Collection reclustering already in progress");
208 return Err(SerializableLibraryError::ReclusterInProgress);
209 }
210
211 let span = tracing::Span::current();
212
213 tokio::task::spawn(
214 async move {
215 let _guard = self.collection_recluster_lock.lock().await;
216 match services::library::recluster(
217 &self.db,
218 self.settings.reclustering,
219 self.interrupt.resubscribe(),
220 )
221 .await
222 {
223 Ok(()) => info!("Collection reclustering complete"),
224 Err(e) => error!("Error in library_recluster: {e}"),
225 }
226
227 let result = self.publish(Event::LibraryReclusterFinished).await;
228 if let Err(e) = result {
229 error!("Error notifying clients that library_recluster_finished: {e}");
230 }
231 }
232 .instrument(span),
233 );
234
235 Ok(())
236 }
237 #[instrument]
239 async fn library_recluster_in_progress(self, context: Context) -> bool {
240 self.collection_recluster_lock.try_lock().is_err()
241 }
242 #[instrument]
244 async fn library_brief(
245 self,
246 context: Context,
247 ) -> Result<LibraryBrief, SerializableLibraryError> {
248 info!("Creating library brief");
249 Ok(services::library::brief(&self.db)
250 .await
251 .tap_err(|e| warn!("Error in library_brief: {e}"))?)
252 }
253 #[instrument]
255 async fn library_full(self, context: Context) -> Result<LibraryFull, SerializableLibraryError> {
256 info!("Creating library full");
257 Ok(services::library::full(&self.db)
258 .await
259 .tap_err(|e| warn!("Error in library_full: {e}"))?)
260 }
261 #[instrument]
263 async fn library_artists_brief(
264 self,
265 context: Context,
266 ) -> Result<Box<[ArtistBrief]>, SerializableLibraryError> {
267 info!("Creating library artists brief");
268 Ok(Artist::read_all_brief(&self.db)
269 .await
270 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
271 .into_boxed_slice())
272 }
273 #[instrument]
275 async fn library_artists_full(
276 self,
277 context: Context,
278 ) -> Result<Box<[Artist]>, SerializableLibraryError> {
279 info!("Creating library artists full");
280 Ok(Artist::read_all(&self.db)
281 .await
282 .tap_err(|e| warn!("Error in library_artists_brief: {e}"))?
283 .into_boxed_slice())
284 }
285 #[instrument]
287 async fn library_albums_brief(
288 self,
289 context: Context,
290 ) -> Result<Box<[AlbumBrief]>, SerializableLibraryError> {
291 info!("Creating library albums brief");
292 Ok(Album::read_all_brief(&self.db)
293 .await
294 .tap_err(|e| warn!("Error in library_albums_brief: {e}"))?
295 .into_boxed_slice())
296 }
297 #[instrument]
299 async fn library_albums_full(
300 self,
301 context: Context,
302 ) -> Result<Box<[Album]>, SerializableLibraryError> {
303 info!("Creating library albums full");
304 Ok(Album::read_all(&self.db)
305 .await
306 .map(std::vec::Vec::into_boxed_slice)
307 .tap_err(|e| warn!("Error in library_albums_full: {e}"))?)
308 }
309 #[instrument]
311 async fn library_songs_brief(
312 self,
313 context: Context,
314 ) -> Result<Box<[SongBrief]>, SerializableLibraryError> {
315 info!("Creating library songs brief");
316 Ok(Song::read_all_brief(&self.db)
317 .await
318 .tap_err(|e| warn!("Error in library_songs_brief: {e}"))?
319 .into_boxed_slice())
320 }
321 #[instrument]
323 async fn library_songs_full(
324 self,
325 context: Context,
326 ) -> Result<Box<[Song]>, SerializableLibraryError> {
327 info!("Creating library songs full");
328 Ok(Song::read_all(&self.db)
329 .await
330 .tap_err(|e| warn!("Error in library_songs_full: {e}"))?
331 .into_boxed_slice())
332 }
333 #[instrument]
335 async fn library_playlists_brief(
336 self,
337 context: Context,
338 ) -> Result<Box<[PlaylistBrief]>, SerializableLibraryError> {
339 info!("Creating library playlists brief");
340 Ok(Playlist::read_all_brief(&self.db)
341 .await
342 .tap_err(|e| warn!("Error in library_playlists_brief: {e}"))?
343 .into_boxed_slice())
344 }
345 #[instrument]
347 async fn library_playlists_full(
348 self,
349 context: Context,
350 ) -> Result<Box<[Playlist]>, SerializableLibraryError> {
351 info!("Creating library playlists full");
352 Ok(Playlist::read_all(&self.db)
353 .await
354 .tap_err(|e| warn!("Error in library_playlists_full: {e}"))?
355 .into_boxed_slice())
356 }
357 #[instrument]
359 async fn library_collections_brief(
360 self,
361 context: Context,
362 ) -> Result<Box<[CollectionBrief]>, SerializableLibraryError> {
363 info!("Creating library collections brief");
364 Ok(Collection::read_all_brief(&self.db)
365 .await
366 .tap_err(|e| warn!("Error in library_collections_brief: {e}"))?
367 .into_boxed_slice())
368 }
369 #[instrument]
371 async fn library_collections_full(
372 self,
373 context: Context,
374 ) -> Result<Box<[Collection]>, SerializableLibraryError> {
375 info!("Creating library collections full");
376 Ok(Collection::read_all(&self.db)
377 .await
378 .tap_err(|e| warn!("Error in library_collections_full: {e}"))?
379 .into_boxed_slice())
380 }
381 #[instrument]
383 async fn library_health(
384 self,
385 context: Context,
386 ) -> Result<LibraryHealth, SerializableLibraryError> {
387 info!("Creating library health");
388 Ok(services::library::health(&self.db)
389 .await
390 .tap_err(|e| warn!("Error in library_health: {e}"))?)
391 }
392 #[instrument]
394 async fn library_song_get(self, context: Context, id: SongId) -> Option<Song> {
395 let id = id.into();
396 info!("Getting song by ID: {id}");
397 Song::read(&self.db, id)
398 .await
399 .tap_err(|e| warn!("Error in library_song_get: {e}"))
400 .unwrap_or_default()
401 }
402 #[instrument]
404 async fn library_song_get_by_path(self, context: Context, path: PathBuf) -> Option<Song> {
405 info!("Getting song by path: {}", path.display());
406 Song::read_by_path(&self.db, path)
407 .await
408 .tap_err(|e| warn!("Error in library_song_get_by_path: {e}"))
409 .unwrap_or_default()
410 }
411 #[instrument]
413 async fn library_song_get_artist(self, context: Context, id: SongId) -> OneOrMany<Artist> {
414 let id = id.into();
415 info!("Getting artist of: {id}");
416 Song::read_artist(&self.db, id)
417 .await
418 .tap_err(|e| warn!("Error in library_song_get_artist: {e}"))
419 .unwrap_or_default()
420 }
421 #[instrument]
423 async fn library_song_get_album(self, context: Context, id: SongId) -> Option<Album> {
424 let id = id.into();
425 info!("Getting album of: {id}");
426 Song::read_album(&self.db, id)
427 .await
428 .tap_err(|e| warn!("Error in library_song_get_album: {e}"))
429 .unwrap_or_default()
430 }
431 #[instrument]
433 async fn library_song_get_playlists(self, context: Context, id: SongId) -> Box<[Playlist]> {
434 let id = id.into();
435 info!("Getting playlists of: {id}");
436 Song::read_playlists(&self.db, id)
437 .await
438 .tap_err(|e| warn!("Error in library_song_get_playlists: {e}"))
439 .ok()
440 .unwrap_or_default()
441 .into()
442 }
443 #[instrument]
445 async fn library_song_get_collections(self, context: Context, id: SongId) -> Box<[Collection]> {
446 let id = id.into();
447 info!("Getting collections of: {id}");
448 Song::read_collections(&self.db, id)
449 .await
450 .tap_err(|e| warn!("Error in library_song_get_collections: {e}"))
451 .ok()
452 .unwrap_or_default()
453 .into()
454 }
455
456 #[instrument]
458 async fn library_album_get(self, context: Context, id: AlbumId) -> Option<Album> {
459 let id = id.into();
460 info!("Getting album by ID: {id}");
461 Album::read(&self.db, id)
462 .await
463 .tap_err(|e| warn!("Error in library_album_get: {e}"))
464 .ok()
465 .flatten()
466 }
467 #[instrument]
469 async fn library_album_get_artist(self, context: Context, id: AlbumId) -> OneOrMany<Artist> {
470 let id = id.into();
471 info!("Getting artists of: {id}");
472 Album::read_artist(&self.db, id)
473 .await
474 .tap_err(|e| warn!("Error in library_album_get_artist: {e}"))
475 .ok()
476 .into()
477 }
478 #[instrument]
480 async fn library_album_get_songs(self, context: Context, id: AlbumId) -> Option<Box<[Song]>> {
481 let id = id.into();
482 info!("Getting songs of: {id}");
483 Album::read_songs(&self.db, id)
484 .await
485 .tap_err(|e| warn!("Error in library_album_get_songs: {e}"))
486 .ok()
487 .map(Vec::into_boxed_slice)
488 }
489 #[instrument]
491 async fn library_artist_get(self, context: Context, id: ArtistId) -> Option<Artist> {
492 let id = id.into();
493 info!("Getting artist by ID: {id}");
494 Artist::read(&self.db, id)
495 .await
496 .tap_err(|e| warn!("Error in library_artist_get: {e}"))
497 .ok()
498 .flatten()
499 }
500 #[instrument]
502 async fn library_artist_get_songs(self, context: Context, id: ArtistId) -> Option<Box<[Song]>> {
503 let id = id.into();
504 info!("Getting songs of: {id}");
505 Artist::read_songs(&self.db, id)
506 .await
507 .tap_err(|e| warn!("Error in library_artist_get_songs: {e}"))
508 .ok()
509 .map(Vec::into_boxed_slice)
510 }
511 #[instrument]
513 async fn library_artist_get_albums(
514 self,
515 context: Context,
516 id: ArtistId,
517 ) -> Option<Box<[Album]>> {
518 let id = id.into();
519 info!("Getting albums of: {id}");
520 Artist::read_albums(&self.db, id)
521 .await
522 .tap_err(|e| warn!("Error in library_artist_get_albums: {e}"))
523 .ok()
524 .map(Vec::into_boxed_slice)
525 }
526
527 #[instrument]
529 async fn daemon_shutdown(self, context: Context) {
530 let terminator = self.terminator.clone();
531 std::thread::Builder::new()
532 .name(String::from("Daemon Shutdown"))
533 .spawn(move || {
534 std::thread::sleep(std::time::Duration::from_secs(1));
535 let terminate_result = terminator
536 .blocking_lock()
537 .terminate(termination::Interrupted::UserInt);
538 if let Err(e) = terminate_result {
539 error!("Error terminating daemon, panicking instead: {e}");
540 panic!("Error terminating daemon: {e}");
541 }
542 })
543 .unwrap();
544 info!("Shutting down daemon in 1 second");
545 }
546
547 #[instrument]
549 async fn state_audio(self, context: Context) -> Option<StateAudio> {
550 debug!("Getting state of audio player");
551 let (tx, rx) = tokio::sync::oneshot::channel();
552
553 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
554
555 rx.await
556 .tap_err(|e| warn!("Error in state_audio: {e}"))
557 .ok()
558 }
559
560 #[instrument]
562 async fn current_artist(self, context: Context) -> OneOrMany<Artist> {
563 info!("Getting current artist");
564 let (tx, rx) = tokio::sync::oneshot::channel();
565
566 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
567
568 if let Some(song) = rx
569 .await
570 .tap_err(|e| warn!("Error in current_artist: {e}"))
571 .ok()
572 .and_then(|state| state.current_song)
573 {
574 Song::read_artist(&self.db, song.id)
575 .await
576 .tap_err(|e| warn!("Error in current_album: {e}"))
577 .unwrap_or_default()
578 } else {
579 OneOrMany::None
580 }
581 }
582 #[instrument]
584 async fn current_album(self, context: Context) -> Option<Album> {
585 info!("Getting current album");
586 let (tx, rx) = tokio::sync::oneshot::channel();
587
588 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
589
590 if let Some(song) = rx
591 .await
592 .tap_err(|e| warn!("Error in current_album: {e}"))
593 .ok()
594 .and_then(|state| state.current_song)
595 {
596 Song::read_album(&self.db, song.id)
597 .await
598 .tap_err(|e| warn!("Error in current_album: {e}"))
599 .unwrap_or_default()
600 } else {
601 None
602 }
603 }
604 #[instrument]
606 async fn current_song(self, context: Context) -> Option<SongBrief> {
607 info!("Getting current song");
608 let (tx, rx) = tokio::sync::oneshot::channel();
609
610 self.audio_kernel.send(AudioCommand::ReportStatus(tx));
611
612 rx.await
613 .tap_err(|e| warn!("Error in current_song: {e}"))
614 .ok()
615 .and_then(|state| state.current_song)
616 }
617
618 #[instrument]
620 async fn rand_artist(self, context: Context) -> Option<ArtistBrief> {
621 info!("Getting random artist");
622 Artist::read_rand(&self.db, 1)
623 .await
624 .tap_err(|e| warn!("Error in rand_artist: {e}"))
625 .ok()
626 .and_then(|results| results.first().cloned())
627 }
628 #[instrument]
630 async fn rand_album(self, context: Context) -> Option<AlbumBrief> {
631 info!("Getting random album");
632 Album::read_rand(&self.db, 1)
633 .await
634 .tap_err(|e| warn!("Error in rand_album: {e}"))
635 .ok()
636 .and_then(|results| results.first().cloned())
637 }
638 #[instrument]
640 async fn rand_song(self, context: Context) -> Option<SongBrief> {
641 info!("Getting random song");
642 Song::read_rand(&self.db, 1)
643 .await
644 .tap_err(|e| warn!("Error in rand_song: {e}"))
645 .ok()
646 .and_then(|results| results.first().cloned())
647 }
648
649 #[instrument]
651 async fn search(self, context: Context, query: String, limit: usize) -> SearchResult {
652 info!("Searching for: {query}");
653 let songs = Song::search(&self.db, &query, limit)
659 .await
660 .tap_err(|e| warn!("Error in search: {e}"))
661 .unwrap_or_default()
662 .into();
663
664 let albums = Album::search(&self.db, &query, limit)
665 .await
666 .tap_err(|e| warn!("Error in search: {e}"))
667 .unwrap_or_default()
668 .into();
669
670 let artists = Artist::search(&self.db, &query, limit)
671 .await
672 .tap_err(|e| warn!("Error in search: {e}"))
673 .unwrap_or_default()
674 .into();
675 SearchResult {
676 songs,
677 albums,
678 artists,
679 }
680 }
681 #[instrument]
683 async fn search_artist(
684 self,
685 context: Context,
686 query: String,
687 limit: usize,
688 ) -> Box<[ArtistBrief]> {
689 info!("Searching for artist: {query}");
690 Artist::search(&self.db, &query, limit)
691 .await
692 .tap_err(|e| {
693 warn!("Error in search_artist: {e}");
694 })
695 .unwrap_or_default()
696 .into()
697 }
698 #[instrument]
700 async fn search_album(
701 self,
702 context: Context,
703 query: String,
704 limit: usize,
705 ) -> Box<[AlbumBrief]> {
706 info!("Searching for album: {query}");
707 Album::search(&self.db, &query, limit)
708 .await
709 .tap_err(|e| {
710 warn!("Error in search_album: {e}");
711 })
712 .unwrap_or_default()
713 .into()
714 }
715 #[instrument]
717 async fn search_song(self, context: Context, query: String, limit: usize) -> Box<[SongBrief]> {
718 info!("Searching for song: {query}");
719 Song::search(&self.db, &query, limit)
720 .await
721 .tap_err(|e| {
722 warn!("Error in search_song: {e}");
723 })
724 .unwrap_or_default()
725 .into()
726 }
727
728 #[instrument]
730 async fn playback_toggle(self, context: Context) {
731 info!("Toggling playback");
732 self.audio_kernel.send(AudioCommand::TogglePlayback);
733 }
734 #[instrument]
736 async fn playback_play(self, context: Context) {
737 info!("Starting playback");
738 self.audio_kernel.send(AudioCommand::Play);
739 }
740 #[instrument]
742 async fn playback_pause(self, context: Context) {
743 info!("Pausing playback");
744 self.audio_kernel.send(AudioCommand::Pause);
745 }
746 #[instrument]
748 async fn playback_stop(self, context: Context) {
749 info!("Stopping playback");
750 self.audio_kernel.send(AudioCommand::Stop);
751 }
752 #[instrument]
754 async fn playback_restart(self, context: Context) {
755 info!("Restarting current song");
756 self.audio_kernel.send(AudioCommand::RestartSong);
757 }
758 #[instrument]
760 async fn playback_skip_forward(self, context: Context, amount: usize) {
761 info!("Skipping forward by {amount} songs");
762 self.audio_kernel
763 .send(AudioCommand::Queue(QueueCommand::SkipForward(amount)));
764 }
765 #[instrument]
767 async fn playback_skip_backward(self, context: Context, amount: usize) {
768 info!("Going back by {amount} songs");
769 self.audio_kernel
770 .send(AudioCommand::Queue(QueueCommand::SkipBackward(amount)));
771 }
772 #[instrument]
775 async fn playback_clear_player(self, context: Context) {
776 info!("Stopping playback");
777 self.audio_kernel.send(AudioCommand::ClearPlayer);
778 }
779 #[instrument]
781 async fn playback_clear(self, context: Context) {
782 info!("Clearing queue and stopping playback");
783 self.audio_kernel
784 .send(AudioCommand::Queue(QueueCommand::Clear));
785 }
786 #[instrument]
788 async fn playback_seek(self, context: Context, seek: SeekType, duration: Duration) {
789 info!("Seeking {seek} by {:.2}s", duration.as_secs_f32());
790 self.audio_kernel.send(AudioCommand::Seek(seek, duration));
791 }
792 #[instrument]
794 async fn playback_repeat(self, context: Context, mode: RepeatMode) {
795 info!("Setting repeat mode to: {mode}");
796 self.audio_kernel
797 .send(AudioCommand::Queue(QueueCommand::SetRepeatMode(mode)));
798 }
799 #[instrument]
801 async fn playback_shuffle(self, context: Context) {
802 info!("Shuffling queue");
803 self.audio_kernel
804 .send(AudioCommand::Queue(QueueCommand::Shuffle));
805 }
806 #[instrument]
809 async fn playback_volume(self, context: Context, volume: f32) {
810 info!("Setting volume to: {volume}",);
811 self.audio_kernel
812 .send(AudioCommand::Volume(VolumeCommand::Set(volume)));
813 }
814 #[instrument]
816 async fn playback_volume_up(self, context: Context, amount: f32) {
817 info!("Increasing volume by: {amount}",);
818 self.audio_kernel
819 .send(AudioCommand::Volume(VolumeCommand::Up(amount)));
820 }
821 #[instrument]
823 async fn playback_volume_down(self, context: Context, amount: f32) {
824 info!("Decreasing volume by: {amount}",);
825 self.audio_kernel
826 .send(AudioCommand::Volume(VolumeCommand::Down(amount)));
827 }
828 #[instrument]
830 async fn playback_volume_toggle_mute(self, context: Context) {
831 info!("Toggling volume mute");
832 self.audio_kernel
833 .send(AudioCommand::Volume(VolumeCommand::ToggleMute));
834 }
835 #[instrument]
837 async fn playback_mute(self, context: Context) {
838 info!("Muting volume");
839 self.audio_kernel
840 .send(AudioCommand::Volume(VolumeCommand::Mute));
841 }
842 #[instrument]
844 async fn playback_unmute(self, context: Context) {
845 info!("Unmuting volume");
846 self.audio_kernel
847 .send(AudioCommand::Volume(VolumeCommand::Unmute));
848 }
849
850 #[instrument]
853 async fn queue_add(
854 self,
855 context: Context,
856 thing: schemas::RecordId,
857 ) -> Result<(), SerializableLibraryError> {
858 info!("Adding thing to queue: {thing}");
859
860 let songs = services::get_songs_from_things(&self.db, &[thing]).await?;
861
862 if songs.is_empty() {
863 return Err(Error::NotFound.into());
864 }
865
866 self.audio_kernel
867 .send(AudioCommand::Queue(QueueCommand::AddToQueue(
868 songs.into_iter().map(Into::into).collect(),
869 )));
870
871 Ok(())
872 }
873 #[instrument]
876 async fn queue_add_list(
877 self,
878 context: Context,
879 list: Vec<schemas::RecordId>,
880 ) -> Result<(), SerializableLibraryError> {
881 info!(
882 "Adding list to queue: ({})",
883 list.iter()
884 .map(ToString::to_string)
885 .collect::<Vec<_>>()
886 .join(", ")
887 );
888
889 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
891
892 self.audio_kernel
893 .send(AudioCommand::Queue(QueueCommand::AddToQueue(
894 songs.into_iter().map(Into::into).collect(),
895 )));
896
897 Ok(())
898 }
899 #[instrument]
902 async fn queue_set_index(self, context: Context, index: usize) {
903 info!("Setting queue index to: {index}");
904
905 self.audio_kernel
906 .send(AudioCommand::Queue(QueueCommand::SetPosition(index)));
907 }
908 #[instrument]
911 async fn queue_remove_range(self, context: Context, range: Range<usize>) {
912 info!("Removing queue range: {range:?}");
913
914 self.audio_kernel
915 .send(AudioCommand::Queue(QueueCommand::RemoveRange(range)));
916 }
917
918 #[instrument]
921 async fn playlist_get_or_create(
922 self,
923 context: Context,
924 name: String,
925 ) -> Result<PlaylistId, SerializableLibraryError> {
926 info!("Creating new playlist: {name}");
927
928 match Playlist::read_by_name(&self.db, name.clone()).await {
930 Ok(Some(playlist)) => return Ok(playlist.id.into()),
931 Err(e) => warn!("Error in playlist_new (looking for existing playlist): {e}"),
932 _ => {}
933 }
934 match Playlist::create(
936 &self.db,
937 Playlist {
938 id: Playlist::generate_id(),
939 name,
940 runtime: Duration::from_secs(0),
941 song_count: 0,
942 },
943 )
944 .await
945 .tap_err(|e| warn!("Error in playlist_new (creating new playlist): {e}"))?
946 {
947 Some(playlist) => Ok(playlist.id.into()),
948 None => Err(Error::NotCreated.into()),
949 }
950 }
951 #[instrument]
953 async fn playlist_remove(
954 self,
955 context: Context,
956 id: PlaylistId,
957 ) -> Result<(), SerializableLibraryError> {
958 let id = id.into();
959 info!("Removing playlist with id: {id}");
960
961 Playlist::delete(&self.db, id)
962 .await?
963 .ok_or(Error::NotFound)?;
964
965 Ok(())
966 }
967 #[instrument]
971 async fn playlist_clone(
972 self,
973 context: Context,
974 id: PlaylistId,
975 ) -> Result<PlaylistId, SerializableLibraryError> {
976 let id = id.into();
977 info!("Cloning playlist with id: {id}");
978
979 let new_playlist = Playlist::create_copy(&self.db, id)
980 .await?
981 .ok_or(Error::NotFound)?;
982
983 Ok(new_playlist.id.into())
984 }
985 #[instrument]
988 async fn playlist_get_id(self, context: Context, name: String) -> Option<PlaylistId> {
989 info!("Getting playlist ID: {name}");
990
991 Playlist::read_by_name(&self.db, name)
992 .await
993 .tap_err(|e| warn!("Error in playlist_get_id: {e}"))
994 .ok()
995 .flatten()
996 .map(|playlist| playlist.id.into())
997 }
998 #[instrument]
1001 async fn playlist_remove_songs(
1002 self,
1003 context: Context,
1004 playlist: PlaylistId,
1005 songs: Vec<SongId>,
1006 ) -> Result<(), SerializableLibraryError> {
1007 let playlist = playlist.into();
1008 let songs = songs.into_iter().map(Into::into).collect::<Vec<_>>();
1009 info!("Removing song from playlist: {playlist} ({songs:?})");
1010
1011 Ok(Playlist::remove_songs(&self.db, playlist, songs).await?)
1012 }
1013 #[instrument]
1016 async fn playlist_add(
1017 self,
1018 context: Context,
1019 playlist: PlaylistId,
1020 thing: schemas::RecordId,
1021 ) -> Result<(), SerializableLibraryError> {
1022 let playlist = playlist.into();
1023 info!("Adding thing to playlist: {playlist} ({thing})");
1024
1025 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &[thing]).await?;
1027
1028 Ok(Playlist::add_songs(
1029 &self.db,
1030 playlist,
1031 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1032 )
1033 .await?)
1034 }
1035 #[instrument]
1038 async fn playlist_add_list(
1039 self,
1040 context: Context,
1041 playlist: PlaylistId,
1042 list: Vec<schemas::RecordId>,
1043 ) -> Result<(), SerializableLibraryError> {
1044 let playlist = playlist.into();
1045 info!(
1046 "Adding list to playlist: {playlist} ({})",
1047 list.iter()
1048 .map(ToString::to_string)
1049 .collect::<Vec<_>>()
1050 .join(", ")
1051 );
1052
1053 let songs: OneOrMany<Song> = services::get_songs_from_things(&self.db, &list).await?;
1055
1056 Ok(Playlist::add_songs(
1057 &self.db,
1058 playlist,
1059 songs.into_iter().map(|s| s.id).collect::<Vec<_>>(),
1060 )
1061 .await?)
1062 }
1063 #[instrument]
1065 async fn playlist_get(self, context: Context, id: PlaylistId) -> Option<Playlist> {
1066 let id = id.into();
1067 info!("Getting playlist by ID: {id}");
1068
1069 Playlist::read(&self.db, id)
1070 .await
1071 .tap_err(|e| warn!("Error in playlist_get: {e}"))
1072 .ok()
1073 .flatten()
1074 }
1075 #[instrument]
1077 async fn playlist_get_songs(self, context: Context, id: PlaylistId) -> Option<Box<[Song]>> {
1078 let id = id.into();
1079 info!("Getting songs in: {id}");
1080 Playlist::read_songs(&self.db, id)
1081 .await
1082 .tap_err(|e| warn!("Error in playlist_get_songs: {e}"))
1083 .ok()
1084 .map(Into::into)
1085 }
1086 #[instrument]
1088 async fn playlist_rename(
1089 self,
1090 context: Context,
1091 id: PlaylistId,
1092 name: String,
1093 ) -> Result<Playlist, SerializableLibraryError> {
1094 let id = id.into();
1095 info!("Renaming playlist: {id} ({name})");
1096 Playlist::update(&self.db, id, PlaylistChangeSet::new().name(name))
1097 .await?
1098 .ok_or(Error::NotFound.into())
1099 }
1100 #[instrument]
1102 async fn playlist_export(
1103 self,
1104 context: Context,
1105 id: PlaylistId,
1106 path: PathBuf,
1107 ) -> Result<(), SerializableLibraryError> {
1108 info!("Exporting playlist to: {}", path.display());
1109
1110 validate_file_path(&path, "m3u", false)?;
1112
1113 let playlist = Playlist::read(&self.db, id.into())
1115 .await
1116 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1117 .ok()
1118 .flatten()
1119 .ok_or(Error::NotFound)?;
1120 let songs = Playlist::read_songs(&self.db, playlist.id)
1122 .await
1123 .tap_err(|e| warn!("Error in playlist_export: {e}"))
1124 .ok()
1125 .unwrap_or_default();
1126
1127 let file = File::create(&path).tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1129 export_playlist(&playlist.name, &songs, file)
1131 .tap_err(|e| warn!("Error in playlist_export: {e}"))?;
1132 info!("Exported playlist to: {}", path.display());
1133 Ok(())
1134 }
1135 #[instrument]
1137 async fn playlist_import(
1138 self,
1139 context: Context,
1140 path: PathBuf,
1141 name: Option<String>,
1142 ) -> Result<PlaylistId, SerializableLibraryError> {
1143 info!("Importing playlist from: {}", path.display());
1144
1145 validate_file_path(&path, "m3u", true)?;
1147
1148 let file = File::open(&path).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1150 let (parsed_name, song_paths) =
1151 import_playlist(file).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
1152
1153 log::debug!("Parsed playlist name: {parsed_name:?}");
1154 log::debug!("Parsed song paths: {song_paths:?}");
1155
1156 let name = match (name, parsed_name) {
1157 (Some(name), _) | (None, Some(name)) => name,
1158 (None, None) => "Imported Playlist".to_owned(),
1159 };
1160
1161 if let Ok(Some(playlist)) = Playlist::read_by_name(&self.db, name.clone()).await {
1163 info!("Playlist \"{name}\" already exists, will not import");
1165 return Ok(playlist.id.into());
1166 }
1167
1168 let playlist = Playlist::create(
1170 &self.db,
1171 Playlist {
1172 id: Playlist::generate_id(),
1173 name,
1174 runtime: Duration::from_secs(0),
1175 song_count: 0,
1176 },
1177 )
1178 .await
1179 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1180 .ok_or(Error::NotCreated)?;
1181
1182 let mut songs = Vec::new();
1184 for path in &song_paths {
1185 let Some(song) = Song::read_by_path(&self.db, path.clone())
1186 .await
1187 .tap_err(|e| warn!("Error in playlist_import: {e}"))?
1188 else {
1189 warn!("Song at {} not found in the library", path.display());
1190 continue;
1191 };
1192
1193 songs.push(song.id);
1194 }
1195
1196 if songs.is_empty() {
1197 return Err(BackupError::NoValidSongs(song_paths.len()).into());
1198 }
1199
1200 Playlist::add_songs(&self.db, playlist.id.clone(), songs)
1202 .await
1203 .tap_err(|e| {
1204 warn!("Error in playlist_import: {e}");
1205 })?;
1206
1207 Ok(playlist.id.into())
1209 }
1210
1211 #[instrument]
1213 async fn collection_get(self, context: Context, id: CollectionId) -> Option<Collection> {
1214 info!("Getting collection by ID: {id:?}");
1215 Collection::read(&self.db, id.into())
1216 .await
1217 .tap_err(|e| warn!("Error in collection_get: {e}"))
1218 .ok()
1219 .flatten()
1220 }
1221 #[instrument]
1223 async fn collection_freeze(
1224 self,
1225 context: Context,
1226 id: CollectionId,
1227 name: String,
1228 ) -> Result<PlaylistId, SerializableLibraryError> {
1229 info!("Freezing collection: {id:?} ({name})");
1230 Ok(Collection::freeze(&self.db, id.into(), name)
1231 .await
1232 .map(|p| p.id.into())?)
1233 }
1234 #[instrument]
1236 async fn collection_get_songs(self, context: Context, id: CollectionId) -> Option<Box<[Song]>> {
1237 let id = id.into();
1238 info!("Getting songs in: {id}");
1239 Collection::read_songs(&self.db, id)
1240 .await
1241 .tap_err(|e| warn!("Error in collection_get_songs: {e}"))
1242 .ok()
1243 .map(Into::into)
1244 }
1245
1246 #[instrument]
1248 async fn radio_get_similar(
1249 self,
1250 context: Context,
1251 things: Vec<schemas::RecordId>,
1252 n: u32,
1253 ) -> Result<Box<[Song]>, SerializableLibraryError> {
1254 info!("Getting the {n} most similar songs to: {things:?}");
1255 Ok(services::radio::get_similar(&self.db, things, n)
1256 .await
1257 .map(Vec::into_boxed_slice)
1258 .tap_err(|e| warn!("Error in radio_get_similar: {e}"))?)
1259 }
1260 #[instrument]
1262 async fn radio_get_similar_ids(
1263 self,
1264 context: Context,
1265 things: Vec<schemas::RecordId>,
1266 n: u32,
1267 ) -> Result<Box<[SongId]>, SerializableLibraryError> {
1268 info!("Getting the {n} most similar songs to: {things:?}");
1269 Ok(services::radio::get_similar(&self.db, things, n)
1270 .await
1271 .map(|songs| songs.into_iter().map(|song| song.id.into()).collect())
1272 .tap_err(|e| warn!("Error in radio_get_similar_songs: {e}"))?)
1273 }
1274
1275 #[instrument]
1278 async fn dynamic_playlist_create(
1279 self,
1280 context: Context,
1281 name: String,
1282 query: Query,
1283 ) -> Result<DynamicPlaylistId, SerializableLibraryError> {
1284 let id = DynamicPlaylist::generate_id();
1285 info!("Creating new DP: {id:?} ({name})");
1286
1287 match DynamicPlaylist::create(&self.db, DynamicPlaylist { id, name, query })
1288 .await
1289 .tap_err(|e| warn!("Error in dynamic_playlist_create: {e}"))?
1290 {
1291 Some(dp) => Ok(dp.id.into()),
1292 None => Err(Error::NotCreated.into()),
1293 }
1294 }
1295 #[instrument]
1297 async fn dynamic_playlist_list(self, context: Context) -> Box<[DynamicPlaylist]> {
1298 info!("Listing DPs");
1299 DynamicPlaylist::read_all(&self.db)
1300 .await
1301 .tap_err(|e| warn!("Error in dynamic_playlist_list: {e}"))
1302 .ok()
1303 .map(Into::into)
1304 .unwrap_or_default()
1305 }
1306 #[instrument]
1308 async fn dynamic_playlist_update(
1309 self,
1310 context: Context,
1311 id: DynamicPlaylistId,
1312 changes: DynamicPlaylistChangeSet,
1313 ) -> Result<DynamicPlaylist, SerializableLibraryError> {
1314 info!("Updating DP: {id:?}, {changes:?}");
1315 DynamicPlaylist::update(&self.db, id.into(), changes)
1316 .await
1317 .tap_err(|e| warn!("Error in dynamic_playlist_update: {e}"))?
1318 .ok_or(Error::NotFound.into())
1319 }
1320 #[instrument]
1322 async fn dynamic_playlist_remove(
1323 self,
1324 context: Context,
1325 id: DynamicPlaylistId,
1326 ) -> Result<(), SerializableLibraryError> {
1327 info!("Removing DP with id: {id:?}");
1328 DynamicPlaylist::delete(&self.db, id.into())
1329 .await?
1330 .ok_or(Error::NotFound)?;
1331 Ok(())
1332 }
1333 #[instrument]
1335 async fn dynamic_playlist_get(
1336 self,
1337 context: Context,
1338 id: DynamicPlaylistId,
1339 ) -> Option<DynamicPlaylist> {
1340 info!("Getting DP by ID: {id:?}");
1341 DynamicPlaylist::read(&self.db, id.into())
1342 .await
1343 .tap_err(|e| warn!("Error in dynamic_playlist_get: {e}"))
1344 .ok()
1345 .flatten()
1346 }
1347 #[instrument]
1349 async fn dynamic_playlist_get_songs(
1350 self,
1351 context: Context,
1352 id: DynamicPlaylistId,
1353 ) -> Option<Box<[Song]>> {
1354 info!("Getting songs in DP: {id:?}");
1355 DynamicPlaylist::run_query_by_id(&self.db, id.into())
1356 .await
1357 .tap_err(|e| warn!("Error in dynamic_playlist_get_songs: {e}"))
1358 .ok()
1359 .flatten()
1360 .map(Into::into)
1361 }
1362 #[instrument]
1364 async fn dynamic_playlist_export(
1365 self,
1366 context: Context,
1367 path: PathBuf,
1368 ) -> Result<(), SerializableLibraryError> {
1369 info!("Exporting dynamic playlists to: {}", path.display());
1370
1371 validate_file_path(&path, "csv", false)?;
1373
1374 let playlists = DynamicPlaylist::read_all(&self.db)
1376 .await
1377 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1378
1379 let file =
1381 File::create(&path).tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1382 let writer = csv::Writer::from_writer(std::io::BufWriter::new(file));
1383 export_dynamic_playlists(&playlists, writer)
1385 .tap_err(|e| warn!("Error in dynamic_playlist_export: {e}"))?;
1386 info!("Exported dynamic playlists to: {}", path.display());
1387 Ok(())
1388 }
1389 #[instrument]
1391 async fn dynamic_playlist_import(
1392 self,
1393 context: Context,
1394 path: PathBuf,
1395 ) -> Result<Vec<DynamicPlaylist>, SerializableLibraryError> {
1396 info!("Importing dynamic playlists from: {}", path.display());
1397
1398 validate_file_path(&path, "csv", true)?;
1400
1401 let file = File::open(&path).tap_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 .tap_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 .tap_err(|e| warn!("Error in dynamic_playlist_import: {e}"))?
1433 .ok_or(Error::NotCreated)?,
1434 );
1435 }
1436
1437 Ok(ids)
1438 }
1439}