1use rand::{prelude::SliceRandom, thread_rng};
2use serde::{Deserialize, Serialize};
3use tracing::instrument;
4
5use crate::state::RepeatMode;
6use mecomp_storage::db::schemas::song::Song;
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9pub struct Queue {
10 songs: Vec<Song>,
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: Song) {
35 self.songs.push(song);
36 }
37
38 #[instrument]
39 pub fn add_songs(&mut self, songs: Vec<Song>) {
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<&Song> {
73 self.current_index.and_then(|index| self.songs.get(index))
74 }
75
76 #[instrument]
77 pub fn next_song(&mut self) -> Option<&Song> {
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<&Song> {
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<&Song> {
125 self.skip_backward(1)
126 }
127
128 #[instrument]
129 pub fn skip_backward(&mut self, n: usize) -> Option<&Song> {
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<&Song> {
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<[Song]> {
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 queue.add_song(song.clone());
284 assert_eq!(queue.len(), 1);
285 assert_eq!(queue.songs[0], song);
286 assert_eq!(queue.current_song(), None);
287
288 Ok(())
289 }
290
291 #[tokio::test]
292 async fn test_add_songs() {
293 init();
294 let db = init_test_database().await.unwrap();
295
296 let songs = vec![
297 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
298 .await
299 .unwrap(),
300 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
301 .await
302 .unwrap(),
303 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
304 .await
305 .unwrap(),
306 ];
307 let mut queue = Queue::new();
308 queue.add_songs(songs.clone());
309 assert_eq!(queue.len(), 3);
310 assert_eq!(queue.queued_songs(), songs.into_boxed_slice());
311 assert_eq!(queue.current_song(), None);
312 }
313
314 #[rstest]
315 #[case::index_oob(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 1, 4, Some(1))]
316 #[case::index_before_current(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 2, 1, Some(1))]
317 #[case::index_after_current(vec![arb_song_case()(),arb_song_case()(),arb_song_case()()], 1, 2, Some(1))]
318 #[case::index_at_current(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 1, 1, Some(0))]
319 #[case::index_at_current_zero(vec![arb_song_case()(), arb_song_case()(), arb_song_case()()], 0, 0, Some(0))]
320 #[case::remove_only_song(vec![arb_song_case()()], 0, 0, None )]
321 #[tokio::test]
322 async fn test_remove_song(
323 #[case] songs: Vec<SongCase>,
324 #[case] current_index_before: usize,
325 #[case] index_to_remove: usize,
326 #[case] expected_current_index_after: Option<usize>,
327 ) {
328 init();
329 let db = init_test_database().await.unwrap();
330 let mut queue = Queue::new();
331
332 for sc in songs {
334 queue.add_song(
335 create_song_with_overrides(&db, sc, SongChangeSet::default())
336 .await
337 .unwrap(),
338 );
339 }
340 queue.set_current_index(current_index_before);
341
342 queue.remove_song(index_to_remove);
344
345 assert_eq!(queue.current_index(), expected_current_index_after);
347 }
348
349 #[rstest]
350 #[case::one_song(arb_vec_and_index( &arb_song_case(), 1..=1, IndexMode::InBounds)())]
351 #[case::many_songs(arb_vec_and_index( &arb_song_case(), 2..=10, IndexMode::InBounds)())]
352 #[case::many_songs_guaranteed_nonzero_index((arb_vec( &arb_song_case(), 2..=10)(), 1))]
353 #[tokio::test]
354 async fn test_shuffle(#[case] params: (Vec<SongCase>, usize)) {
355 init();
356 let (songs, index) = params;
357 let db = init_test_database().await.unwrap();
358 let mut queue = Queue::default();
359
360 for sc in songs {
362 queue.add_song(
363 create_song_with_overrides(&db, sc, SongChangeSet::default())
364 .await
365 .unwrap(),
366 );
367 }
368 queue.set_current_index(index);
369
370 let current_song = queue.current_song().cloned();
371
372 queue.shuffle();
374
375 assert_eq!(queue.current_song().cloned(), current_song);
377 assert_eq!(queue.current_index(), Some(0));
378 }
379
380 #[tokio::test]
381 async fn test_next_previous_basic() -> anyhow::Result<()> {
382 init();
383 let db = init_test_database().await.unwrap();
384
385 let mut queue = Queue::new();
386 let song1 =
387 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default()).await?;
388 let song2 =
389 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default()).await?;
390 queue.add_song(song1.clone());
391 queue.add_song(song2.clone());
392 assert_eq!(queue.next_song(), Some(&song1));
393 assert_eq!(queue.next_song(), Some(&song2));
394 assert_eq!(queue.previous_song(), Some(&song1));
395 assert_eq!(queue.previous_song(), None);
396
397 queue.clear();
398 assert_eq!(queue.next_song(), None);
399 assert_eq!(queue.previous_song(), None);
400
401 Ok(())
402 }
403
404 #[tokio::test]
405 async fn test_next_song_with_rp_one() {
406 init();
407 let db = init_test_database().await.unwrap();
408
409 let mut queue = Queue::new();
410 queue.set_repeat_mode(RepeatMode::One);
411 let song1 = create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
412 .await
413 .unwrap();
414 let song2 = create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default())
415 .await
416 .unwrap();
417 queue.add_song(song1.clone());
418 queue.add_song(song2.clone());
419
420 assert_eq!(queue.current_song(), None);
421 assert_eq!(queue.next_song(), Some(&song1));
422 assert_eq!(queue.current_song(), Some(&song1));
423 assert_eq!(queue.next_song(), Some(&song1));
424 queue.skip_forward(1);
425 assert_eq!(queue.current_song(), Some(&song2));
426 assert_eq!(queue.next_song(), Some(&song2));
427 queue.skip_forward(1);
428 assert_eq!(queue.current_song(), None);
429 assert_eq!(queue.next_song(), None);
430 }
431
432 #[template]
433 #[rstest]
434 #[case::more_than_len( arb_vec(&arb_song_case(), 4..=5 )(), 7 )]
435 #[case::way_more_than_len( arb_vec(&arb_song_case(), 3..=5 )(), 11 )]
436 #[case::skip_len( arb_vec(&arb_song_case(), 5..=5 )(), 5 )]
437 #[case::skip_len_twice( arb_vec(&arb_song_case(), 5..=5 )(), 10 )]
438 #[case::less_than_len( arb_vec(&arb_song_case(), 4..=5 )(), 3 )]
439 #[case::skip_one( arb_vec(&arb_song_case(), 2..=5 )(), 1 )]
440 #[timeout(std::time::Duration::from_secs(30))]
441 pub fn skip_song_test_template(#[case] songs: Vec<SongCase>, #[case] skip: usize) {}
442
443 #[apply(skip_song_test_template)]
444 #[tokio::test]
445 async fn test_skip_song_rp_none(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
446 init();
447 let db = init_test_database().await.unwrap();
448
449 let mut queue = Queue::new();
450 let len = songs.len();
451 for sc in songs {
452 queue.add_song(create_song_with_overrides(&db, sc, SongChangeSet::default()).await?);
453 }
454 queue.set_repeat_mode(RepeatMode::None);
455
456 queue.skip_forward(skip);
457
458 if skip <= len {
459 assert_eq!(
460 queue.current_song(),
461 queue.get(skip - 1),
462 "len: {len}, skip: {skip}, current_index: {current_index}",
463 current_index = queue.current_index.unwrap_or_default()
464 );
465 } else {
466 assert_eq!(
467 queue.current_song(),
468 None,
469 "len: {len}, skip: {skip}, current_index: {current_index}",
470 current_index = queue.current_index.unwrap_or_default()
471 );
472 }
473
474 Ok(())
475 }
476
477 #[apply(skip_song_test_template)]
478 #[tokio::test]
479 async fn test_skip_song_rp_one(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
480 init();
481 let db = init_test_database().await.unwrap();
482
483 let mut queue = Queue::new();
484 let len = songs.len();
485 for sc in songs {
486 queue.add_song(create_song_with_overrides(&db, sc, SongChangeSet::default()).await?);
487 }
488 queue.set_repeat_mode(RepeatMode::One);
489
490 queue.skip_forward(skip);
491
492 if skip <= len {
493 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!(
503 queue.current_song(),
504 None,
505 "len: {len}, skip: {skip}, current_index: {current_index}",
506 current_index = queue.current_index.unwrap_or_default()
507 );
508 }
509
510 Ok(())
511 }
512
513 #[apply(skip_song_test_template)]
514 #[tokio::test]
515 async fn test_next_song_rp_all(songs: Vec<SongCase>, skip: usize) -> anyhow::Result<()> {
516 init();
517 let db = init_test_database().await.unwrap();
518
519 let mut queue = Queue::new();
520 let len = songs.len();
521 for sc in songs {
522 queue.add_song(create_song_with_overrides(&db, sc, SongChangeSet::default()).await?);
523 }
524 queue.set_repeat_mode(RepeatMode::All);
525
526 queue.skip_forward(skip);
527
528 assert_eq!(
529 queue.current_song(),
530 queue.get((skip - 1) % len),
531 "len: {len}, skip: {skip}, current_index: {current_index}",
532 current_index = queue.current_index.unwrap_or_default()
533 );
534
535 Ok(())
536 }
537
538 #[rstest]
539 #[case(RepeatMode::None)]
540 #[case(RepeatMode::One)]
541 #[case(RepeatMode::All)]
542 #[test]
543 fn test_set_repeat_mode(#[case] repeat_mode: RepeatMode) {
544 let mut queue = Queue::new();
545 queue.set_repeat_mode(repeat_mode);
546 assert_eq!(queue.repeat_mode, repeat_mode);
547 }
548
549 #[rstest]
550 #[case::within_range( arb_vec(&arb_song_case(), 5..=10 )(), 3 )]
551 #[case::at_start( arb_vec(&arb_song_case(), 5..=10 )(), 0 )]
552 #[case::at_end( arb_vec(&arb_song_case(), 10..=10 )(), 9 )]
553 #[case::empty( arb_vec(&arb_song_case(),0..=0)(), 0)]
554 #[case::out_of_range( arb_vec(&arb_song_case(), 5..=10 )(), 15 )]
555 #[tokio::test]
556 async fn test_set_current_index(
557 #[case] songs: Vec<SongCase>,
558 #[case] index: usize,
559 ) -> anyhow::Result<()> {
560 init();
561 let db = init_test_database().await?;
562
563 let mut queue = Queue::new();
564 let len = songs.len();
565 for sc in songs {
566 queue.add_song(create_song_with_overrides(&db, sc, SongChangeSet::default()).await?);
567 }
568
569 queue.set_current_index(index);
570
571 if len == 0 {
572 assert_eq!(queue.current_index, None);
573 } else if index >= len {
574 assert_eq!(queue.current_index, Some(len - 1));
575 } else {
576 assert_eq!(queue.current_index, Some(index.min(len - 1)));
577 }
578
579 Ok(())
580 }
581
582 #[rstest]
583 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::InRange )() )]
584 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::BeforeRange )() )]
585 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::AfterRangeInBounds )() )]
586 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::OutOfBounds )() )]
587 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::Standard, RangeIndexMode::InBounds )() )]
588 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::OutOfBounds,RangeEndMode::Standard, RangeIndexMode::InRange )() )]
589 #[case( arb_vec_and_range_and_index(&arb_song_case(), 0..=0,RangeStartMode::Zero,RangeEndMode::Start, RangeIndexMode::InBounds )() )]
590 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10, RangeStartMode::Standard, RangeEndMode::Start, RangeIndexMode::InBounds)() )]
591 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::InBounds )() )]
592 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::InRange )() )]
593 #[case( arb_vec_and_range_and_index(&arb_song_case(), 5..=10,RangeStartMode::Standard,RangeEndMode::OutOfBounds, RangeIndexMode::BeforeRange )() )]
594 #[tokio::test]
595 async fn test_remove_range(
596 #[case] params: (Vec<SongCase>, std::ops::Range<usize>, Option<usize>),
597 ) -> anyhow::Result<()> {
598 init();
599 let (songs, range, index) = params;
600 let len = songs.len();
601 let db = init_test_database().await?;
602
603 let mut queue = Queue::new();
604 for sc in songs {
605 queue.add_song(create_song_with_overrides(&db, sc, SongChangeSet::default()).await?);
606 }
607
608 if let Some(index) = index {
609 queue.set_current_index(index);
610 }
611
612 let unmodified_songs = queue.clone();
613
614 queue.remove_range(range.clone());
615
616 let start = range.start;
617 let end = range.end.min(len);
618
619 if start >= len || start == end {
625 assert_eq!(queue.len(), len);
626 } else if start == 0 && end >= len {
627 assert_eq!(queue.len(), 0);
628 assert_eq!(queue.current_index, None);
629 } else {
630 assert_eq!(queue.len(), len - (end.min(len) - start));
631 for i in 0..start {
632 assert_eq!(queue.get(i), unmodified_songs.get(i));
633 }
634 for i in end..len {
635 assert_eq!(queue.get(i - (end - start)), unmodified_songs.get(i));
636 }
637 }
638 Ok(())
639 }
640}