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