aboutsummaryrefslogtreecommitdiffstats
path: root/src/daemon.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-27 20:26:56 +0200
committersalmonik <raisersoft@proton.me>2026-05-27 20:26:56 +0200
commit09c78a14cb7dc83c7711b6e58025d43272e9eaf7 (patch)
treee5eec691695c3beff320a2a2a9c2d60c2524ec55 /src/daemon.rs
parentadd licensing (diff)
downloadrsplayer-09c78a14cb7dc83c7711b6e58025d43272e9eaf7.tar.gz
rsplayer-09c78a14cb7dc83c7711b6e58025d43272e9eaf7.zip
play [track] [-a/--album] option
if supplied when a track is played, the rest of the album will be queued after the track
Diffstat (limited to 'src/daemon.rs')
-rw-r--r--src/daemon.rs141
1 files changed, 141 insertions, 0 deletions
diff --git a/src/daemon.rs b/src/daemon.rs
index 63510ea..20f4e7e 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -681,6 +681,147 @@ fn process_command(
681 "Error accessing queue\n".to_string() 681 "Error accessing queue\n".to_string()
682 } 682 }
683 } 683 }
684 _ if cmd.starts_with("play-album ") => {
685 let rest = &cmd[11..].trim();
686 let (position, query_str): (&str, &str) = if let Some((pos, p)) = rest.split_once(' ') {
687 if pos == "now" || pos == "next" || pos == "last" {
688 (pos, p)
689 } else {
690 ("last", rest)
691 }
692 } else {
693 ("last", rest)
694 };
695
696 if query_str.is_empty() {
697 return "Usage: play-album [now|next|last] <path_or_query>\n".to_string();
698 }
699
700 let path = std::path::PathBuf::from(query_str);
701
702 // Resolve the source track path and its album continuation tracks.
703 // If the argument is an existing file, extract metadata from the path
704 // and look up the album in the DB. Otherwise search the DB by query.
705 let (track_path, album_paths, display_name): (PathBuf, Vec<PathBuf>, String) =
706 if path.is_file() {
707 let album_paths: Vec<PathBuf> = match Config::load() {
708 Ok(config) => match crate::database::Database::open(&config) {
709 Ok(db) => db
710 .find_album_tracks_after_path(&path)
711 .unwrap_or_default()
712 .into_iter()
713 .map(|t| t.path)
714 .collect(),
715 Err(_) => Vec::new(),
716 },
717 Err(_) => Vec::new(),
718 };
719 let name = path
720 .file_name()
721 .and_then(|n| n.to_str())
722 .unwrap_or("unknown")
723 .to_string();
724 (path, album_paths, name)
725 } else {
726 match Config::load() {
727 Ok(config) => match crate::database::Database::open(&config) {
728 Ok(db) => match db.search_tracks(query_str) {
729 Ok(tracks) if !tracks.is_empty() => {
730 let db_track = &tracks[0];
731 let album_tracks = db
732 .get_album_tracks_after(
733 db_track.album_id,
734 db_track.track_number,
735 &db_track.path,
736 )
737 .unwrap_or_default();
738 let display = format!(
739 "{} - {}",
740 db_track.artist_name.as_deref().unwrap_or("unknown"),
741 db_track.title,
742 );
743 let paths: Vec<PathBuf> =
744 album_tracks.into_iter().map(|t| t.path).collect();
745 (db_track.path.clone(), paths, display)
746 }
747 Ok(_) => {
748 return format!("No tracks found matching '{}'\n", query_str);
749 }
750 Err(e) => return format!("Database error: {}\n", e),
751 },
752 Err(e) => return format!("Database error: {}\n", e),
753 },
754 Err(e) => return format!("Config error: {}\n", e),
755 }
756 };
757
758 if let Ok(mut q) = queue.lock() {
759 let was_empty = q.is_empty();
760 match position {
761 "now" => {
762 if let Ok(mut s) = scrobble_state.lock() {
763 try_scrobble_current(scrobbler, &mut s);
764 }
765 for t in album_paths.iter().rev() {
766 q.insert_after_current(t.clone());
767 }
768 q.insert_after_current(track_path.clone());
769 if !was_empty {
770 let new_pos = q.position() + 1;
771 q.set_position(new_pos);
772 }
773 if let Some(track) = q.track_at(q.position()) {
774 audio.play(track.path.clone());
775 send_now_playing(scrobbler, track);
776 if let Ok(mut s) = scrobble_state.lock() {
777 s.start(track.clone());
778 }
779 let fmt = Config::load().ok().and_then(|c| c.display_format.clone());
780 notification::notify_track(track, fmt.as_deref());
781 }
782 format!(
783 "Playing {} with {} album track(s)\n",
784 display_name,
785 album_paths.len()
786 )
787 }
788 "next" => {
789 for t in album_paths.iter().rev() {
790 q.insert_after_current(t.clone());
791 }
792 q.insert_after_current(track_path.clone());
793 format!(
794 "Queued next: {} with {} album track(s)\n",
795 display_name,
796 album_paths.len()
797 )
798 }
799 _ => {
800 q.add_track(track_path.clone());
801 for t in &album_paths {
802 q.add_track(t.clone());
803 }
804 if was_empty
805 && !audio.is_playing()
806 && let Some(track) = q.current()
807 {
808 audio.play(track.path.clone());
809 send_now_playing(scrobbler, track);
810 if let Ok(mut s) = scrobble_state.lock() {
811 s.start(track.clone());
812 }
813 }
814 format!(
815 "Queued: {} with {} album track(s)\n",
816 display_name,
817 album_paths.len()
818 )
819 }
820 }
821 } else {
822 "Error accessing queue\n".to_string()
823 }
824 }
684 _ if cmd.starts_with("play ") => { 825 _ if cmd.starts_with("play ") => {
685 let rest = &cmd[5..].trim(); 826 let rest = &cmd[5..].trim();
686 let (position, path_str): (&str, &str) = if let Some((pos, p)) = rest.split_once(' ') { 827 let (position, path_str): (&str, &str) = if let Some((pos, p)) = rest.split_once(' ') {