1use rand::{prelude::SliceRandom, thread_rng};
2use serde::{Deserialize, Serialize};
3use tracing::instrument;
4
5use crate::state::RepeatMode;
6use mecomp_storage::db::schemas::song::SongBrief;
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9pub struct Queue {
10 songs: Vec<SongBrief>,
11 current_index: Option<usize>,
12 repeat_mode: RepeatMode,
13}
14
15impl Default for Queue {
16 #[inline]
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl Queue {
23 #[must_use]
24 #[inline]
25 pub const fn new() -> Self {
26 Self {
27 songs: Vec::new(),
28 current_index: None,
29 repeat_mode: RepeatMode::None,
30 }
31 }
32
33 #[instrument]
34 pub fn add_song(&mut self, song: SongBrief) {
35 self.songs.push(song);
36 }
37
38 #[instrument]
39 pub fn add_songs(&mut self, songs: Vec<SongBrief>) {
40 self.songs.extend(songs);
41 }
42
43 #[instrument]
44 pub fn remove_song(&mut self, index: usize) {
45 if index >= self.len() {
46 return;
47 }
48
49 match self.current_index {
50 Some(current_index)
51 if current_index > index || (current_index == index && current_index > 0) =>
52 {
53 self.current_index = Some(current_index - 1);
54 }
55 Some(_) if self.len() <= 1 => {
56 self.current_index = None;
57 }
58 _ => {}
59 }
60
61 self.songs.remove(index);
62 }
63
64 #[instrument]
65 pub fn clear(&mut self) {
66 self.songs.clear();
67 self.current_index = None;
68 }
69
70 #[must_use]
71 #[instrument]
72 pub fn current_song(&self) -> Option<&SongBrief> {
73 self.current_index.and_then(|index| self.songs.get(index))
74 }
75
76 #[instrument]
77 pub fn next_song(&mut self) -> Option<&SongBrief> {
78 if self.repeat_mode == RepeatMode::One && self.current_index.is_some() {
79 self.current_song()
80 } else {
81 self.skip_forward(1)
82 }
83 }
84
85 #[instrument]
89 pub fn skip_forward(&mut self, n: usize) -> Option<&SongBrief> {
90 match self.current_index {
91 Some(current_index) if current_index + n < self.songs.len() => {
92 self.current_index = Some(current_index + n);
93 self.current_index.and_then(|index| self.songs.get(index))
94 }
95 Some(current_index) => {
96 match self.repeat_mode {
97 RepeatMode::None | RepeatMode::One => {
98 self.current_index = None;
101 self.songs.clear();
102 None
103 }
104 RepeatMode::All => {
105 self.current_index = Some((current_index + n) % self.songs.len());
108 self.current_index.and_then(|index| self.songs.get(index))
109 }
110 }
111 }
112 None => {
113 if self.songs.is_empty() || n == 0 {
114 return None;
115 }
116
117 self.current_index = Some(0);
118 self.skip_forward(n - 1)
119 }
120 }
121 }
122
123 #[instrument]
124 pub fn previous_song(&mut self) -> Option<&SongBrief> {
125 self.skip_backward(1)
126 }
127
128 #[instrument]
129 pub fn skip_backward(&mut self, n: usize) -> Option<&SongBrief> {
130 match self.current_index {
131 Some(current_index) if current_index >= n => {
132 self.current_index = Some(current_index - n);
133 self.current_index.and_then(|index| self.songs.get(index))
134 }
135 _ => {
136 self.current_index = None;
137 None
138 }
139 }
140 }
141
142 #[instrument]
143 pub fn set_repeat_mode(&mut self, repeat_mode: RepeatMode) {
144 self.repeat_mode = repeat_mode;
145 }
146
147 #[must_use]
148 #[inline]
149 pub const fn get_repeat_mode(&self) -> RepeatMode {
150 self.repeat_mode
151 }
152
153 #[instrument]
154 pub fn shuffle(&mut self) {
155 match self.current_index {
157 Some(current_index) if current_index != 0 && !self.is_empty() => {
158 self.songs.swap(0, current_index);
159 self.current_index = Some(0);
160 }
161 _ => {}
162 }
163 if self.len() <= 1 {
164 return;
165 }
166 self.songs[1..].shuffle(&mut thread_rng());
168 }
169
170 #[must_use]
171 #[instrument]
172 pub fn get(&self, index: usize) -> Option<&SongBrief> {
173 self.songs.get(index)
174 }
175
176 #[must_use]
177 #[instrument]
178 pub fn len(&self) -> usize {
179 self.songs.len()
180 }
181
182 #[must_use]
183 #[instrument]
184 pub fn is_empty(&self) -> bool {
185 self.songs.is_empty()
186 }
187
188 #[must_use]
189 #[inline]
190 pub const fn current_index(&self) -> Option<usize> {
191 self.current_index
192 }
193
194 #[must_use]
195 #[instrument]
196 pub fn queued_songs(&self) -> Box<[SongBrief]> {
197 self.songs.clone().into_boxed_slice()
198 }
199
200 #[instrument]
202 pub fn set_current_index(&mut self, index: usize) {
203 if self.songs.is_empty() {
204 self.current_index = None;
205 } else {
206 self.current_index = Some(index.min(self.songs.len() - 1));
207 }
208 }
209
210 #[instrument]
214 pub fn remove_range(&mut self, range: std::ops::Range<usize>) {
215 if range.is_empty() || self.is_empty() {
216 return;
217 }
218 let current_index = self.current_index.unwrap_or_default();
219 let range_end = range.end.min(self.songs.len());
220 let range_start = range.start.min(range_end);
221
222 self.songs.drain(range_start..range_end);
223
224 if current_index >= range_start && current_index < range_end {
225 self.current_index = Some(range_start);
227 } else if current_index >= range_end {
228 self.current_index = Some(current_index - (range_end - range_start));
230 }
231
232 if self.current_index.unwrap_or_default() >= self.songs.len() || self.is_empty() {
234 self.current_index = None;
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::state::RepeatMode;
243 use crate::test_utils::init;
244
245 use mecomp_storage::db::schemas::song::SongChangeSet;
246 use mecomp_storage::test_utils::{
247 IndexMode, RangeEndMode, RangeIndexMode, RangeStartMode, SongCase, arb_song_case, arb_vec,
248 arb_vec_and_index, arb_vec_and_range_and_index, create_song_with_overrides,
249 init_test_database,
250 };
251
252 use pretty_assertions::assert_eq;
253 use rstest::*;
254 use rstest_reuse;
255 use rstest_reuse::{apply, template};
256
257 #[test]
258 fn test_new_queue() {
259 let mut queue = Queue::default();
260 assert_eq!(queue.len(), 0);
261 assert_eq!(queue.current_index(), None);
262 assert_eq!(queue.get_repeat_mode(), RepeatMode::None);
263
264 assert_eq!(queue.current_song(), None);
265 assert_eq!(queue.next_song(), None);
266 assert_eq!(queue.current_index, None);
267 assert_eq!(queue.previous_song(), None);
268 assert_eq!(queue.current_index, None);
269 }
270
271 #[rstest]
272 #[case(arb_song_case()())]
273 #[case(arb_song_case()())]
274 #[case(arb_song_case()())]
275 #[tokio::test]
276 async fn test_add_song(#[case] song: SongCase) -> anyhow::Result<()> {
277 init();
278
279 let db = init_test_database().await.unwrap();
280
281 let mut queue = Queue::new();
282 let song = create_song_with_overrides(&db, song, SongChangeSet::default()).await?;
283 let song: SongBrief = song.into();
284 queue.add_song(song.clone());
285 assert_eq!(queue.len(), 1);
286 assert_eq!(queue.songs[0], song);
287 assert_eq!(queue.current_song(), None);
288
289 Ok(())
290 }
291
292 #[tokio::test]
293 async fn test_add_songs() {
294 init();
295 let db = init_test_database().await.unwrap();
296
297 let songs = vec![
298 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
299 .await
300 .unwrap()
301 .into(),
302 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
303 .await
304 .unwrap()
305 .into(),
306 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
307 .await
308 .unwrap()
309 .into(),
310 ];
311 let mut queue = Queue::new();
312 queue.add_songs(songs.clone());
313 assert_eq!(queue.len(), 3);
314 assert_eq!(queue.queued_songs(), songs.into_boxed_slice());
315 assert_eq!(queue.current_song(), None);
316 }
317
318 #[rstest]
319 #[case::index_oob(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 1, 4, Some(1))]
320 #[case::index_before_current(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 2, 1, Some(1))]
321 #[case::index_after_current(vec![arb_song_case()(),arb_song_case()(),arb_song_case()()], 1, 2, Some(1))]
322 #[case::index_at_current(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 1, 1, Some(0))]
323 #[case::index_at_current_zero(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 0, 0, Some(0))]
324 #[case::remove_only_song(vec![arb_song_case()()], 0, 0, None )]
325 #[tokio::test]
326 async fn test_remove_song(
327 #[case] songs: Vec<SongCase>,
328 #[case] current_index_before: usize,
329 #[case] index_to_remove: usize,
330 #[case] expected_current_index_after: Option<usize>,
331 ) {
332 init();
333 let db = init_test_database().await.unwrap();
334 let mut queue = Queue::new();
335
336 for sc in songs {
338 queue.add_song(
339 create_song_with_overrides(&db, sc, SongChangeSet::default())
340 .await
341 .unwrap()
342 .into(),
343 );
344 }
345 queue.set_current_index(current_index_before);
346
347 queue.remove_song(index_to_remove);
349
350 assert_eq!(queue.current_index(), expected_current_index_after);
352 }
353
354 #[rstest]
355 #[case::one_song(arb_vec_and_index( &arb_song_case(), 1..=1, IndexMode::InBounds)())]
356 #[case::many_songs(arb_vec_and_index( &arb_song_case(), 2..=10, IndexMode::InBounds)())]
357 #[case::many_songs_guaranteed_nonzero_index((arb_vec( &arb_song_case(), 2..=10)(), 1))]
358 #[tokio::test]
359 async fn test_shuffle(#[case] params: (Vec<SongCase>, usize)) {
360 init();
361 let (songs, index) = params;
362 let db = init_test_database().await.unwrap();
363 let mut queue = Queue::default();
364
365 for sc in songs {
367 queue.add_song(
368 create_song_with_overrides(&db, sc, SongChangeSet::default())
369 .await
370 .unwrap()
371 .into(),
372 );
373 }
374 queue.set_current_index(index);
375
376 let current_song = queue.current_song().cloned();
377
378 queue.shuffle();
380
381 assert_eq!(queue.current_song().cloned(), current_song);
383 assert_eq!(queue.current_index(), Some(0));
384 }
385
386 #[tokio::test]
387 async fn test_next_previous_basic() -> anyhow::Result<()> {
388 init();
389 let db = init_test_database().await.unwrap();
390
391 let mut queue = Queue::new();
392 let song1: SongBrief =
393 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
394 .await?
395 .into();
396 let song2: SongBrief =
397 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
398 .await?
399 .into();
400 queue.add_song(song1.clone());
401 queue.add_song(song2.clone());
402 assert_eq!(queue.next_song(), Some(&song1));
403 assert_eq!(queue.next_song(), Some(&song2));
404 assert_eq!(queue.previous_song(), Some(&song1));
405 assert_eq!(queue.previous_song(), None);
406
407 queue.clear();
408 assert_eq!(queue.next_song(), None);
409 assert_eq!(queue.previous_song(), None);
410
411 Ok(())
412 }
413
414 #[tokio::test]
415 async fn test_next_song_with_rp_one() {
416 init();
417 let db = init_test_database().await.unwrap();
418
419 let mut queue = Queue::new();
420 queue.set_repeat_mode(RepeatMode::One);
421 let song1: SongBrief =
422 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
423 .await
424 .unwrap()
425 .into();
426 let song2: SongBrief =
427 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
428 .await
429 .unwrap()
430 .into();
431 queue.add_song(song1.clone());
432 queue.add_song(song2.clone());
433
434 assert_eq!(queue.current_song(), None);
435 assert_eq!(queue.next_song(), Some(&song1));
436 assert_eq!(queue.current_song(), Some(&song1));
437 assert_eq!(queue.next_song(), Some(&song1));
438 queue.skip_forward(1);
439 assert_eq!(queue.current_song(), Some(&song2));
440 assert_eq!(queue.next_song(), Some(&song2));
441 queue.skip_forward(1);
442 assert_eq!(queue.current_song(), None);
443 assert_eq!(queue.next_song(), None);
444 }
445
446 #[template]
447 #[rstest]
448 #[case::more_than_len( arb_vec(&arb_song_case(), 4..=5 )(), 7 )]
449 #[case::way_more_than_len( arb_vec(&arb_song_case(), 3..=5 )(), 11 )]
450 #[case::skip_len( arb_vec(&arb_song_case(), 5..=5 )(), 5 )]
451 #[case::skip_len_twice( arb_vec(&arb_song_case(), 5..=5 )(), 10 )]
452 #[case::less_than_len( arb_vec(&arb_song_case(), 4..=5 )(), 3 )]
453 #[case::skip_one( arb_vec(&arb_song_case(), 2..=5 )(), 1 )]
454 #[timeout(std::time::Duration::from_secs(30))]
455 pub fn skip_song_test_template(#[case] songs: Vec<SongCase>, #[case] skip: usize) {}
456
457 #[apply(skip_song_test_template)]
458 #[tokio::test]
459 async fn test_skip_song_rp_none(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
460 init();
461 let db = init_test_database().await.unwrap();
462
463 let mut queue = Queue::new();
464 let len = songs.len();
465 for sc in songs {
466 queue.add_song(
467 create_song_with_overrides(&db, sc, SongChangeSet::default())
468 .await?
469 .into(),
470 );
471 }
472 queue.set_repeat_mode(RepeatMode::None);
473
474 queue.skip_forward(skip);
475
476 if skip <= len {
477 assert_eq!(
478 queue.current_song(),
479 queue.get(skip - 1),
480 "len: {len}, skip: {skip}, current_index: {current_index}",
481 current_index = queue.current_index.unwrap_or_default()
482 );
483 } else {
484 assert_eq!(
485 queue.current_song(),
486 None,
487 "len: {len}, skip: {skip}, current_index: {current_index}",
488 current_index = queue.current_index.unwrap_or_default()
489 );
490 }
491
492 Ok(())
493 }
494
495 #[apply(skip_song_test_template)]
496 #[tokio::test]
497 async fn test_skip_song_rp_one(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
498 init();
499 let db = init_test_database().await.unwrap();
500
501 let mut queue = Queue::new();
502 let len = songs.len();
503 for sc in songs {
504 queue.add_song(
505 create_song_with_overrides(&db, sc, SongChangeSet::default())
506 .await?
507 .into(),
508 );
509 }
510 queue.set_repeat_mode(RepeatMode::One);
511
512 queue.skip_forward(skip);
513
514 if skip <= len {
515 assert_eq!(
517 queue.current_song(),
518 queue.get(skip - 1),
519 "len: {len}, skip: {skip}, current_index: {current_index}",
520 current_index = queue.current_index.unwrap_or_default()
521 );
522 } else {
523 assert_eq!(
525 queue.current_song(),
526 None,
527 "len: {len}, skip: {skip}, current_index: {current_index}",
528 current_index = queue.current_index.unwrap_or_default()
529 );
530 }
531
532 Ok(())
533 }
534
535 #[apply(skip_song_test_template)]
536 #[tokio::test]
537 async fn test_next_song_rp_all(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
538 init();
539 let db = init_test_database().await.unwrap();
540
541 let mut queue = Queue::new();
542 let len = songs.len();
543 for sc in songs {
544 queue.add_song(
545 create_song_with_overrides(&db, sc, SongChangeSet::default())
546 .await?
547 .into(),
548 );
549 }
550 queue.set_repeat_mode(RepeatMode::All);
551
552 queue.skip_forward(skip);
553
554 assert_eq!(
555 queue.current_song(),
556 queue.get((skip - 1) % len),
557 "len: {len}, skip: {skip}, current_index: {current_index}",
558 current_index = queue.current_index.unwrap_or_default()
559 );
560
561 Ok(())
562 }
563
564 #[rstest]
565 #[case(RepeatMode::None)]
566 #[case(RepeatMode::One)]
567 #[case(RepeatMode::All)]
568 #[test]
569 fn test_set_repeat_mode(#[case] repeat_mode: RepeatMode) {
570 let mut queue = Queue::new();
571 queue.set_repeat_mode(repeat_mode);
572 assert_eq!(queue.repeat_mode, repeat_mode);
573 }
574
575 #[rstest]
576 #[case::within_range( arb_vec(&arb_song_case(), 5..=10 )(), 3 )]
577 #[case::at_start( arb_vec(&arb_song_case(), 5..=10 )(), 0 )]
578 #[case::at_end( arb_vec(&arb_song_case(), 10..=10 )(), 9 )]
579 #[case::empty( arb_vec(&arb_song_case(),0..=0)(), 0)]
580 #[case::out_of_range( arb_vec(&arb_song_case(), 5..=10 )(), 15 )]
581 #[tokio::test]
582 async fn test_set_current_index(
583 #[case] songs: Vec<SongCase>,
584 #[case] index: usize,
585 ) -> anyhow::Result<()> {
586 init();
587 let db = init_test_database().await?;
588
589 let mut queue = Queue::new();
590 let len = songs.len();
591 for sc in songs {
592 queue.add_song(
593 create_song_with_overrides(&db, sc, SongChangeSet::default())
594 .await?
595 .into(),
596 );
597 }
598
599 queue.set_current_index(index);
600
601 if len == 0 {
602 assert_eq!(queue.current_index, None);
603 } else if index >= len {
604 assert_eq!(queue.current_index, Some(len - 1));
605 } else {
606 assert_eq!(queue.current_index, Some(index.min(len - 1)));
607 }
608
609 Ok(())
610 }
611
612 #[rstest]
613 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::InRange )() )]
614 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::BeforeRange )() )]
615 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::AfterRangeInBounds )() )]
616 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::OutOfBounds )() )]
617 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::InBounds )() )]
618 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::OutOfBounds,RangeEndMode::Standard, RangeIndexMode::InRange )() )]
619 #[case( arb_vec_and_range_and_index(&arb_song_case(), 0..=0,RangeStartMode::Zero,RangeEndMode::Start, RangeIndexMode::InBounds )() )]
620 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10, RangeStartMode::Standard, RangeEndMode::Start, RangeIndexMode::InBounds)() )]
621 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::InBounds )() )]
622 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::InRange )() )]
623 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::BeforeRange )() )]
624 #[tokio::test]
625 async fn test_remove_range(
626 #[case] params: (Vec<SongCase>, std::ops::Range<usize>, Option<usize>),
627 ) -> anyhow::Result<()> {
628 init();
629 let (songs, range, index) = params;
630 let len = songs.len();
631 let db = init_test_database().await?;
632
633 let mut queue = Queue::new();
634 for sc in songs {
635 queue.add_song(
636 create_song_with_overrides(&db, sc, SongChangeSet::default())
637 .await?
638 .into(),
639 );
640 }
641
642 if let Some(index) = index {
643 queue.set_current_index(index);
644 }
645
646 let unmodified_songs = queue.clone();
647
648 queue.remove_range(range.clone());
649
650 let start = range.start;
651 let end = range.end.min(len);
652
653 if start >= len || start == end {
659 assert_eq!(queue.len(), len);
660 } else if start == 0 && end >= len {
661 assert_eq!(queue.len(), 0);
662 assert_eq!(queue.current_index, None);
663 } else {
664 assert_eq!(queue.len(), len - (end.min(len) - start));
665 for i in 0..start {
666 assert_eq!(queue.get(i), unmodified_songs.get(i));
667 }
668 for i in end..len {
669 assert_eq!(queue.get(i - (end - start)), unmodified_songs.get(i));
670 }
671 }
672 Ok(())
673 }
674}