aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO.md36
-rw-r--r--src/queue.rs22
-rw-r--r--tests/queue.rs50
3 files changed, 65 insertions, 43 deletions
diff --git a/TODO.md b/TODO.md
index 59ba564..e06d624 100644
--- a/TODO.md
+++ b/TODO.md
@@ -62,35 +62,13 @@
62 62
63## Remaining Work 63## Remaining Work
64 64
65### Phase 1: Play Command Rewrite 65### Phase 1-3: Completed ✓
66 66
67- [ ] **Rewrite `rsp play` behavior** 67- [x] **Play command rewrite** - No args queues to end (play if queue empty + not playing), -i inserts and plays, -n inserts without playing, removed -l flag
68 - No options: Add to end of queue. If queue is empty AND player is in PLAY state, play immediately 68- [x] **Queue display rewrite** - Returns full output (client can pipe to pager), removed daemon-side pager (fails without TTY)
69 - `-i` / `--now`: Add after current track, then skip to it (insert + play) 69- [x] **Shuffle rewrite** - Always remembers original queue, shuffle none keeps played tracks in place, album shuffle groups by album
70 - `-n` / `--next`: Add after current track, don't skip 70
71 - Remove `-l` / `--last` flag (redundant, it's default behavior) 71### Optional Features
72 - Update CLI definitions in `src/cli.rs`
73 - Update daemon handler in `src/daemon.rs`
74
75### Phase 2: Queue Display Rewrite
76
77- [ ] **Rewrite `rsp queue` behavior**
78 - If queue rows <= terminal rows, display as-is (no pager needed)
79 - If queue rows > terminal rows, pipe to pager (e.g., `less -R`)
80 - Get terminal size using `terminal_size` crate or `ansi_term` / `crossterm`
81 - Update queue display logic in `src/daemon.rs`
82
83### Phase 3: Shuffle Rewrite
84
85- [ ] **Rewrite shuffle mode**
86 - Always remember original queue (add `original_tracks: Vec<Track>` field)
87 - `rsp shuffle all`: Shuffle tracks after current position once, store original queue
88 - `rsp shuffle none`: Restore from original queue - keep already-played tracks in place, insert unplayed tracks in original order after current position
89 - `rsp shuffle album`: Shuffle tracks within each album's range separately (don't mix albums)
90 - Update `Queue` struct in `src/queue.rs` with original queue storage
91 - Update shuffle logic in `src/queue.rs` and daemon handlers in `src/daemon.rs`
92
93### Phase 4: Optional Features
94 72
95- [ ] **Persist queue state** - Save queue to disk so it survives daemon restarts. 73- [ ] **Persist queue state** - Save queue to disk so it survives daemon restarts.
96 74
diff --git a/src/queue.rs b/src/queue.rs
index f556ed3..edd973f 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -191,8 +191,12 @@ impl Queue {
191 191
192 let current_pos = self.position; 192 let current_pos = self.position;
193 let indices_len = self.shuffled_indices.len().min(10); 193 let indices_len = self.shuffled_indices.len().min(10);
194 eprintln!("DEBUG restore: current_pos={}, tracks.len={}, shuffled_indices={:?}", 194 eprintln!(
195 current_pos, self.tracks.len(), &self.shuffled_indices[..indices_len]); 195 "DEBUG restore: current_pos={}, tracks.len={}, shuffled_indices={:?}",
196 current_pos,
197 self.tracks.len(),
198 &self.shuffled_indices[..indices_len]
199 );
196 200
197 // Find which tracks have been played (positions 0..=current_pos in shuffled order) 201 // Find which tracks have been played (positions 0..=current_pos in shuffled order)
198 let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new(); 202 let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new();
@@ -200,8 +204,12 @@ impl Queue {
200 if let Some(&idx) = self.shuffled_indices.get(i) 204 if let Some(&idx) = self.shuffled_indices.get(i)
201 && let Some(track) = self.tracks.get(idx) 205 && let Some(track) = self.tracks.get(idx)
202 { 206 {
203 eprintln!("DEBUG: played track at shuffled pos {} -> track idx {} = {:?}", 207 eprintln!(
204 i, idx, track.path.file_name()); 208 "DEBUG: played track at shuffled pos {} -> track idx {} = {:?}",
209 i,
210 idx,
211 track.path.file_name()
212 );
205 played_track_paths.push(track.path.clone()); 213 played_track_paths.push(track.path.clone());
206 } 214 }
207 } 215 }
@@ -220,7 +228,11 @@ impl Queue {
220 for (i, orig_track) in self.original_tracks.iter().enumerate() { 228 for (i, orig_track) in self.original_tracks.iter().enumerate() {
221 let is_played = played_track_paths.iter().any(|p| p == &orig_track.path); 229 let is_played = played_track_paths.iter().any(|p| p == &orig_track.path);
222 if !is_played { 230 if !is_played {
223 eprintln!("DEBUG: adding unplayed from original idx {}: {:?}", i, orig_track.path.file_name()); 231 eprintln!(
232 "DEBUG: adding unplayed from original idx {}: {:?}",
233 i,
234 orig_track.path.file_name()
235 );
224 new_tracks.push(orig_track.clone()); 236 new_tracks.push(orig_track.clone());
225 } 237 }
226 } 238 }
diff --git a/tests/queue.rs b/tests/queue.rs
index e511612..438fa23 100644
--- a/tests/queue.rs
+++ b/tests/queue.rs
@@ -549,7 +549,10 @@ fn test_shuffle_all_then_none_exact_scenario() {
549 q.set_position(3); 549 q.set_position(3);
550 550
551 let current_before = q.current().unwrap().path.clone(); 551 let current_before = q.current().unwrap().path.clone();
552 println!("Current before restore: {:?}", current_before.file_name().unwrap()); 552 println!(
553 "Current before restore: {:?}",
554 current_before.file_name().unwrap()
555 );
553 println!("Position: {}", q.position()); 556 println!("Position: {}", q.position());
554 557
555 // Now shuffle none 558 // Now shuffle none
@@ -557,7 +560,11 @@ fn test_shuffle_all_then_none_exact_scenario() {
557 560
558 println!("After restore:"); 561 println!("After restore:");
559 for i in 0..q.len() { 562 for i in 0..q.len() {
560 println!(" {}: {:?}", i+1, q.track_at(i).unwrap().path.file_name().unwrap()); 563 println!(
564 " {}: {:?}",
565 i + 1,
566 q.track_at(i).unwrap().path.file_name().unwrap()
567 );
561 } 568 }
562 569
563 // After restore: 570 // After restore:
@@ -567,7 +574,10 @@ fn test_shuffle_all_then_none_exact_scenario() {
567 assert_eq!(q.position(), 3, "position should stay same"); 574 assert_eq!(q.position(), 3, "position should stay same");
568 575
569 let current_after = q.current().unwrap().path.clone(); 576 let current_after = q.current().unwrap().path.clone();
570 assert_eq!(current_before, current_after, "current track should stay same"); 577 assert_eq!(
578 current_before, current_after,
579 "current track should stay same"
580 );
571} 581}
572 582
573#[test] 583#[test]
@@ -597,10 +607,17 @@ fn test_shuffle_restore_simulation() {
597 607
598 println!("After 4 next() calls:"); 608 println!("After 4 next() calls:");
599 println!(" Position: {}", q.position()); 609 println!(" Position: {}", q.position());
600 println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap()); 610 println!(
611 " Current: {:?}",
612 q.current().unwrap().path.file_name().unwrap()
613 );
601 println!(" Tracks:"); 614 println!(" Tracks:");
602 for i in 0..q.len() { 615 for i in 0..q.len() {
603 println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); 616 println!(
617 " {}: {:?}",
618 i,
619 q.track_at(i).unwrap().path.file_name().unwrap()
620 );
604 } 621 }
605 622
606 let current_path = q.current().unwrap().path.clone(); 623 let current_path = q.current().unwrap().path.clone();
@@ -611,20 +628,35 @@ fn test_shuffle_restore_simulation() {
611 628
612 println!("After shuffle none:"); 629 println!("After shuffle none:");
613 println!(" Position: {}", q.position()); 630 println!(" Position: {}", q.position());
614 println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap()); 631 println!(
632 " Current: {:?}",
633 q.current().unwrap().path.file_name().unwrap()
634 );
615 println!(" Tracks:"); 635 println!(" Tracks:");
616 for i in 0..q.len() { 636 for i in 0..q.len() {
617 println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); 637 println!(
638 " {}: {:?}",
639 i,
640 q.track_at(i).unwrap().path.file_name().unwrap()
641 );
618 } 642 }
619 643
620 // Assertions 644 // Assertions
621 assert_eq!(q.position(), pos_before, "position should be preserved"); 645 assert_eq!(q.position(), pos_before, "position should be preserved");
622 assert_eq!(q.current().unwrap().path, current_path, "current track should be preserved"); 646 assert_eq!(
647 q.current().unwrap().path,
648 current_path,
649 "current track should be preserved"
650 );
623 651
624 // The key requirement: tracks before current position should remain in played order 652 // The key requirement: tracks before current position should remain in played order
625 // and tracks after should be remaining from original in original order 653 // and tracks after should be remaining from original in original order
626 let pos = q.position(); 654 let pos = q.position();
627 for i in 0..pos { 655 for i in 0..pos {
628 println!("Played track {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); 656 println!(
657 "Played track {}: {:?}",
658 i,
659 q.track_at(i).unwrap().path.file_name().unwrap()
660 );
629 } 661 }
630} 662}