diff options
| author | salmonik <raisersoft@proton.me> | 2026-06-25 14:57:00 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-06-25 14:57:00 +0200 |
| commit | 7f42d61cf45d77b0162c60805393ccf56169efd0 (patch) | |
| tree | 989530f3e0de1bb780f22739bf3351594ae27883 | |
| parent | I love \r (diff) | |
| download | rsplayer-7f42d61cf45d77b0162c60805393ccf56169efd0.tar.gz rsplayer-7f42d61cf45d77b0162c60805393ccf56169efd0.zip | |
fix -a to work properly
| -rw-r--r-- | src/cli.rs | 2 | ||||
| -rw-r--r-- | src/daemon.rs | 186 |
2 files changed, 113 insertions, 75 deletions
| @@ -36,7 +36,7 @@ pub enum Commands { | |||
| 36 | /// Add to queue after current track (don't skip) | 36 | /// Add to queue after current track (don't skip) |
| 37 | #[arg(short = 'n', long = "next")] | 37 | #[arg(short = 'n', long = "next")] |
| 38 | next: bool, | 38 | next: bool, |
| 39 | /// Queue rest of album after this track (requires a track query) | 39 | /// Queue the entire album containing this track (start at this track) |
| 40 | #[arg(short = 'a', long = "album")] | 40 | #[arg(short = 'a', long = "album")] |
| 41 | album: bool, | 41 | album: bool, |
| 42 | #[arg(value_name = "QUERY")] | 42 | #[arg(value_name = "QUERY")] |
diff --git a/src/daemon.rs b/src/daemon.rs index 1434555..61d021b 100644 --- a/src/daemon.rs +++ b/src/daemon.rs | |||
| @@ -770,71 +770,100 @@ fn process_command( | |||
| 770 | 770 | ||
| 771 | let path = std::path::PathBuf::from(query_str); | 771 | let path = std::path::PathBuf::from(query_str); |
| 772 | 772 | ||
| 773 | // Resolve the source track path and its album continuation tracks. | 773 | // Resolve the full album containing the requested track, ordered by |
| 774 | // If the argument is an existing file, extract metadata from the path | 774 | // track_number. The album is rotated so playback starts at the |
| 775 | // and look up the album in the DB. Otherwise search the DB by query. | 775 | // requested track while the *entire* album is queued (auto-advance |
| 776 | let (track_path, album_paths, display_name): (PathBuf, Vec<PathBuf>, String) = | 776 | // wraps through any earlier tracks). If the argument is an existing |
| 777 | if path.is_file() { | 777 | // file, derive the album from its metadata; otherwise query the DB |
| 778 | let album_paths: Vec<PathBuf> = match Config::load() { | 778 | // by hierarchical path or free-text search. |
| 779 | Ok(config) => match crate::database::Database::open(&config) { | 779 | let (album_tracks, display_name): (Vec<PathBuf>, String) = if path.is_file() { |
| 780 | Ok(db) => db | 780 | let canon = std::fs::canonicalize(&path).unwrap_or_else(|_| path.clone()); |
| 781 | .find_album_tracks_after_path(&path) | 781 | let (artist, album, _, _, _) = crate::database::extract_metadata(&canon); |
| 782 | .unwrap_or_default() | 782 | let full: Vec<PathBuf> = match Config::load() { |
| 783 | .into_iter() | 783 | Ok(config) => match crate::database::Database::open(&config) { |
| 784 | .map(|t| t.path) | 784 | Ok(db) => db |
| 785 | .collect(), | 785 | .find_tracks_by_album(&artist, &album) |
| 786 | Err(_) => Vec::new(), | 786 | .unwrap_or_default() |
| 787 | }, | 787 | .into_iter() |
| 788 | .map(|t| t.path) | ||
| 789 | .collect(), | ||
| 788 | Err(_) => Vec::new(), | 790 | Err(_) => Vec::new(), |
| 789 | }; | 791 | }, |
| 790 | let name = path | 792 | Err(_) => Vec::new(), |
| 791 | .file_name() | 793 | }; |
| 792 | .and_then(|n| n.to_str()) | 794 | let start_idx = full.iter().position(|p| *p == canon).unwrap_or(0); |
| 793 | .unwrap_or("unknown") | 795 | let mut rotated: Vec<PathBuf> = full[start_idx..].to_vec(); |
| 794 | .to_string(); | 796 | rotated.extend_from_slice(&full[..start_idx]); |
| 795 | (path, album_paths, name) | 797 | if rotated.is_empty() { |
| 796 | } else { | 798 | rotated.push(path.clone()); |
| 797 | match Config::load() { | 799 | } |
| 798 | Ok(config) => match crate::database::Database::open(&config) { | 800 | let name = path |
| 799 | Ok(db) => { | 801 | .file_name() |
| 800 | let tracks = match db.resolve_path(query_str) { | 802 | .and_then(|n| n.to_str()) |
| 801 | Ok(Some(t)) => t, | 803 | .unwrap_or("unknown") |
| 802 | Ok(None) => match db.search_tracks(query_str) { | 804 | .to_string(); |
| 803 | Ok(t) => t, | 805 | (rotated, name) |
| 804 | Err(e) => return format!("Database error: {}\n", e), | 806 | } else { |
| 805 | }, | 807 | match Config::load() { |
| 808 | Ok(config) => match crate::database::Database::open(&config) { | ||
| 809 | Ok(db) => { | ||
| 810 | let tracks = match db.resolve_path(query_str) { | ||
| 811 | Ok(Some(t)) => t, | ||
| 812 | Ok(None) => match db.search_tracks(query_str) { | ||
| 813 | Ok(t) => t, | ||
| 806 | Err(e) => return format!("Database error: {}\n", e), | 814 | Err(e) => return format!("Database error: {}\n", e), |
| 807 | }; | 815 | }, |
| 808 | if tracks.is_empty() { | 816 | Err(e) => return format!("Database error: {}\n", e), |
| 809 | return format!("No tracks found matching '{}'\n", query_str); | 817 | }; |
| 810 | } | 818 | if tracks.is_empty() { |
| 811 | let db_track = &tracks[0]; | 819 | return format!("No tracks found matching '{}'\n", query_str); |
| 812 | let album_paths: Vec<PathBuf> = if tracks.len() > 1 { | 820 | } |
| 813 | // resolve_path returned full album — use remaining tracks | 821 | let (full, display): (Vec<crate::database::Track>, String) = |
| 814 | tracks[1..].iter().map(|t| t.path.clone()).collect() | 822 | if tracks.len() > 1 { |
| 823 | // resolve_path returned the full album | ||
| 824 | // ("Artist/Album" query) — keep order. | ||
| 825 | let d = format!( | ||
| 826 | "{} - {}", | ||
| 827 | tracks[0].artist_name.as_deref().unwrap_or("unknown"), | ||
| 828 | tracks[0].title, | ||
| 829 | ); | ||
| 830 | (tracks, d) | ||
| 815 | } else { | 831 | } else { |
| 816 | db.get_album_tracks_after( | 832 | // Single track from "Artist/Album/Track" |
| 817 | db_track.album_id, | 833 | // or a search result — fetch the whole |
| 818 | db_track.track_number, | 834 | // album and rotate so the requested |
| 819 | &db_track.path, | 835 | // track plays first. |
| 820 | ) | 836 | let db_track = &tracks[0]; |
| 821 | .unwrap_or_default() | 837 | let full = match db.find_tracks_by_album( |
| 822 | .into_iter() | 838 | db_track.artist_name.as_deref().unwrap_or(""), |
| 823 | .map(|t| t.path) | 839 | db_track.album_title.as_deref().unwrap_or(""), |
| 824 | .collect() | 840 | ) { |
| 841 | Ok(f) if !f.is_empty() => f, | ||
| 842 | _ => vec![db_track.clone()], | ||
| 843 | }; | ||
| 844 | let start_idx = full | ||
| 845 | .iter() | ||
| 846 | .position(|t| t.path == db_track.path) | ||
| 847 | .unwrap_or(0); | ||
| 848 | let mut rotated: Vec<crate::database::Track> = | ||
| 849 | full[start_idx..].to_vec(); | ||
| 850 | rotated.extend_from_slice(&full[..start_idx]); | ||
| 851 | let d = format!( | ||
| 852 | "{} - {}", | ||
| 853 | db_track.artist_name.as_deref().unwrap_or("unknown"), | ||
| 854 | db_track.title, | ||
| 855 | ); | ||
| 856 | (rotated, d) | ||
| 825 | }; | 857 | }; |
| 826 | let display = format!( | 858 | let album_tracks: Vec<PathBuf> = |
| 827 | "{} - {}", | 859 | full.iter().map(|t| t.path.clone()).collect(); |
| 828 | db_track.artist_name.as_deref().unwrap_or("unknown"), | 860 | (album_tracks, display) |
| 829 | db_track.title, | 861 | } |
| 830 | ); | 862 | Err(e) => return format!("Database error: {}\n", e), |
| 831 | (db_track.path.clone(), album_paths, display) | 863 | }, |
| 832 | } | 864 | Err(e) => return format!("Config error: {}\n", e), |
| 833 | Err(e) => return format!("Database error: {}\n", e), | 865 | } |
| 834 | }, | 866 | }; |
| 835 | Err(e) => return format!("Config error: {}\n", e), | ||
| 836 | } | ||
| 837 | }; | ||
| 838 | 867 | ||
| 839 | if let Ok(mut q) = queue.lock() { | 868 | if let Ok(mut q) = queue.lock() { |
| 840 | let was_empty = q.is_empty(); | 869 | let was_empty = q.is_empty(); |
| @@ -843,11 +872,16 @@ fn process_command( | |||
| 843 | if let Ok(mut s) = scrobble_state.lock() { | 872 | if let Ok(mut s) = scrobble_state.lock() { |
| 844 | try_scrobble_current(scrobbler, &mut s); | 873 | try_scrobble_current(scrobbler, &mut s); |
| 845 | } | 874 | } |
| 846 | for t in album_paths.iter().rev() { | 875 | // album_tracks[0] is the track to start at; insert the |
| 847 | q.insert_after_current(t.clone()); | 876 | // whole block in correct order. |
| 848 | } | 877 | if was_empty { |
| 849 | q.insert_after_current(track_path.clone()); | 878 | for t in &album_tracks { |
| 850 | if !was_empty { | 879 | q.add_track(t.clone()); |
| 880 | } | ||
| 881 | } else { | ||
| 882 | for t in album_tracks.iter().rev() { | ||
| 883 | q.insert_after_current(t.clone()); | ||
| 884 | } | ||
| 851 | let new_pos = q.position() + 1; | 885 | let new_pos = q.position() + 1; |
| 852 | q.set_position(new_pos); | 886 | q.set_position(new_pos); |
| 853 | } | 887 | } |
| @@ -863,23 +897,27 @@ fn process_command( | |||
| 863 | format!( | 897 | format!( |
| 864 | "Playing {} with {} album track(s)\n", | 898 | "Playing {} with {} album track(s)\n", |
| 865 | display_name, | 899 | display_name, |
| 866 | album_paths.len() | 900 | album_tracks.len().saturating_sub(1) |
| 867 | ) | 901 | ) |
| 868 | } | 902 | } |
| 869 | "next" => { | 903 | "next" => { |
| 870 | for t in album_paths.iter().rev() { | 904 | if was_empty { |
| 871 | q.insert_after_current(t.clone()); | 905 | for t in &album_tracks { |
| 906 | q.add_track(t.clone()); | ||
| 907 | } | ||
| 908 | } else { | ||
| 909 | for t in album_tracks.iter().rev() { | ||
| 910 | q.insert_after_current(t.clone()); | ||
| 911 | } | ||
| 872 | } | 912 | } |
| 873 | q.insert_after_current(track_path.clone()); | ||
| 874 | format!( | 913 | format!( |
| 875 | "Queued next: {} with {} album track(s)\n", | 914 | "Queued next: {} with {} album track(s)\n", |
| 876 | display_name, | 915 | display_name, |
| 877 | album_paths.len() | 916 | album_tracks.len().saturating_sub(1) |
| 878 | ) | 917 | ) |
| 879 | } | 918 | } |
| 880 | _ => { | 919 | _ => { |
| 881 | q.add_track(track_path.clone()); | 920 | for t in &album_tracks { |
| 882 | for t in &album_paths { | ||
| 883 | q.add_track(t.clone()); | 921 | q.add_track(t.clone()); |
| 884 | } | 922 | } |
| 885 | if was_empty | 923 | if was_empty |
| @@ -895,7 +933,7 @@ fn process_command( | |||
| 895 | format!( | 933 | format!( |
| 896 | "Queued: {} with {} album track(s)\n", | 934 | "Queued: {} with {} album track(s)\n", |
| 897 | display_name, | 935 | display_name, |
| 898 | album_paths.len() | 936 | album_tracks.len().saturating_sub(1) |
| 899 | ) | 937 | ) |
| 900 | } | 938 | } |
| 901 | } | 939 | } |
