1use crate::api::LastFmApiClient;
2use crate::r#trait::LastFmBaseClient;
3use crate::{Album, AlbumPage, Result, Track, TrackPage};
4
5use async_trait::async_trait;
6
7#[cfg_attr(feature = "mock", mockall::automock)]
13#[async_trait(?Send)]
14pub trait AsyncPaginatedIterator<T> {
15 async fn next(&mut self) -> Result<Option<T>>;
26
27 async fn collect_all(&mut self) -> Result<Vec<T>> {
33 let mut items = Vec::new();
34 while let Some(item) = self.next().await? {
35 items.push(item);
36 }
37 Ok(items)
38 }
39
40 async fn take(&mut self, n: usize) -> Result<Vec<T>> {
49 let mut items = Vec::new();
50 for _ in 0..n {
51 match self.next().await? {
52 Some(item) => items.push(item),
53 None => break,
54 }
55 }
56 Ok(items)
57 }
58
59 fn current_page(&self) -> u32;
63
64 fn total_pages(&self) -> Option<u32> {
69 None }
71}
72
73pub struct ArtistTracksIterator<C: LastFmBaseClient> {
83 client: C,
84 artist: String,
85 album_iterator: Option<ArtistAlbumsIterator<C>>,
86 current_album_tracks: Option<AlbumTracksIterator<C>>,
87 track_buffer: Vec<Track>,
88 finished: bool,
89}
90
91#[async_trait(?Send)]
92impl<C: LastFmBaseClient + Clone> AsyncPaginatedIterator<Track> for ArtistTracksIterator<C> {
93 async fn next(&mut self) -> Result<Option<Track>> {
94 if self.finished {
96 return Ok(None);
97 }
98
99 while self.track_buffer.is_empty() {
101 if self.current_album_tracks.is_none() {
103 if self.album_iterator.is_none() {
105 self.album_iterator = Some(ArtistAlbumsIterator::new(
106 self.client.clone(),
107 self.artist.clone(),
108 ));
109 }
110
111 if let Some(ref mut album_iter) = self.album_iterator {
113 if let Some(album) = album_iter.next().await? {
114 log::debug!(
115 "Processing album '{}' for artist '{}'",
116 album.name,
117 self.artist
118 );
119 self.current_album_tracks = Some(AlbumTracksIterator::new(
121 self.client.clone(),
122 album.name.clone(),
123 self.artist.clone(),
124 ));
125 } else {
126 log::debug!("No more albums for artist '{}'", self.artist);
128 self.finished = true;
129 return Ok(None);
130 }
131 }
132 }
133
134 if let Some(ref mut album_tracks) = self.current_album_tracks {
136 if let Some(track) = album_tracks.next().await? {
137 self.track_buffer.push(track);
138 } else {
139 log::debug!(
141 "Finished processing current album for artist '{}'",
142 self.artist
143 );
144 self.current_album_tracks = None;
145 }
147 }
148 }
149
150 Ok(self.track_buffer.pop())
152 }
153
154 fn current_page(&self) -> u32 {
155 if let Some(ref album_iter) = self.album_iterator {
157 album_iter.current_page()
158 } else {
159 0
160 }
161 }
162
163 fn total_pages(&self) -> Option<u32> {
164 if let Some(ref album_iter) = self.album_iterator {
166 album_iter.total_pages()
167 } else {
168 None
169 }
170 }
171}
172
173impl<C: LastFmBaseClient + Clone> ArtistTracksIterator<C> {
174 pub fn new(client: C, artist: String) -> Self {
178 Self {
179 client,
180 artist,
181 album_iterator: None,
182 current_album_tracks: None,
183 track_buffer: Vec::new(),
184 finished: false,
185 }
186 }
187}
188
189pub struct ArtistTracksDirectIterator<C: LastFmBaseClient> {
197 client: C,
198 artist: String,
199 current_page: u32,
200 has_more: bool,
201 buffer: Vec<Track>,
202 total_pages: Option<u32>,
203 tracks_yielded: u32,
204}
205
206#[async_trait(?Send)]
207impl<C: LastFmBaseClient> AsyncPaginatedIterator<Track> for ArtistTracksDirectIterator<C> {
208 async fn next(&mut self) -> Result<Option<Track>> {
209 if self.buffer.is_empty() {
211 if let Some(page) = self.next_page().await? {
212 self.buffer = page.tracks;
213 self.buffer.reverse(); }
215 }
216
217 if let Some(track) = self.buffer.pop() {
218 self.tracks_yielded += 1;
219 Ok(Some(track))
220 } else {
221 Ok(None)
222 }
223 }
224
225 fn current_page(&self) -> u32 {
226 self.current_page.saturating_sub(1)
227 }
228
229 fn total_pages(&self) -> Option<u32> {
230 self.total_pages
231 }
232}
233
234impl<C: LastFmBaseClient> ArtistTracksDirectIterator<C> {
235 pub fn new(client: C, artist: String) -> Self {
239 Self {
240 client,
241 artist,
242 current_page: 1,
243 has_more: true,
244 buffer: Vec::new(),
245 total_pages: None,
246 tracks_yielded: 0,
247 }
248 }
249
250 pub async fn next_page(&mut self) -> Result<Option<TrackPage>> {
254 if !self.has_more {
255 return Ok(None);
256 }
257
258 log::debug!(
259 "Fetching page {} of {} tracks (yielded {} tracks so far)",
260 self.current_page,
261 self.artist,
262 self.tracks_yielded
263 );
264
265 let page = self
266 .client
267 .get_artist_tracks_page(&self.artist, self.current_page)
268 .await?;
269
270 self.has_more = page.has_next_page;
271 self.current_page += 1;
272 self.total_pages = page.total_pages;
273
274 Ok(Some(page))
275 }
276
277 pub fn total_pages(&self) -> Option<u32> {
281 self.total_pages
282 }
283}
284
285pub struct ArtistAlbumsIterator<C: LastFmBaseClient> {
290 client: C,
291 artist: String,
292 current_page: u32,
293 has_more: bool,
294 buffer: Vec<Album>,
295 total_pages: Option<u32>,
296}
297
298#[async_trait(?Send)]
299impl<C: LastFmBaseClient> AsyncPaginatedIterator<Album> for ArtistAlbumsIterator<C> {
300 async fn next(&mut self) -> Result<Option<Album>> {
301 if self.buffer.is_empty() {
303 if let Some(page) = self.next_page().await? {
304 self.buffer = page.albums;
305 self.buffer.reverse(); }
307 }
308
309 Ok(self.buffer.pop())
310 }
311
312 fn current_page(&self) -> u32 {
313 self.current_page.saturating_sub(1)
314 }
315
316 fn total_pages(&self) -> Option<u32> {
317 self.total_pages
318 }
319}
320
321impl<C: LastFmBaseClient> ArtistAlbumsIterator<C> {
322 pub fn new(client: C, artist: String) -> Self {
326 Self {
327 client,
328 artist,
329 current_page: 1,
330 has_more: true,
331 buffer: Vec::new(),
332 total_pages: None,
333 }
334 }
335
336 pub async fn next_page(&mut self) -> Result<Option<AlbumPage>> {
340 if !self.has_more {
341 return Ok(None);
342 }
343
344 let page = self
345 .client
346 .get_artist_albums_page(&self.artist, self.current_page)
347 .await?;
348
349 self.has_more = page.has_next_page;
350 self.current_page += 1;
351 self.total_pages = page.total_pages;
352
353 Ok(Some(page))
354 }
355
356 pub fn total_pages(&self) -> Option<u32> {
360 self.total_pages
361 }
362}
363
364pub struct RecentTracksIterator<C: LastFmBaseClient> {
370 client: C,
371 current_page: u32,
372 has_more: bool,
373 buffer: Vec<Track>,
374 stop_at_timestamp: Option<u64>,
375}
376
377#[async_trait(?Send)]
378impl<C: LastFmBaseClient> AsyncPaginatedIterator<Track> for RecentTracksIterator<C> {
379 async fn next(&mut self) -> Result<Option<Track>> {
380 if self.buffer.is_empty() {
382 if !self.has_more {
383 return Ok(None);
384 }
385
386 let page = self
387 .client
388 .get_recent_tracks_page(self.current_page)
389 .await?;
390
391 if page.tracks.is_empty() {
392 self.has_more = false;
393 return Ok(None);
394 }
395
396 self.has_more = page.has_next_page;
397
398 if let Some(stop_timestamp) = self.stop_at_timestamp {
400 let mut filtered_tracks = Vec::new();
401 for track in page.tracks {
402 if let Some(track_timestamp) = track.timestamp {
403 if track_timestamp <= stop_timestamp {
404 self.has_more = false;
405 break;
406 }
407 }
408 filtered_tracks.push(track);
409 }
410 self.buffer = filtered_tracks;
411 } else {
412 self.buffer = page.tracks;
413 }
414
415 self.buffer.reverse(); self.current_page += 1;
417 }
418
419 Ok(self.buffer.pop())
420 }
421
422 fn current_page(&self) -> u32 {
423 self.current_page.saturating_sub(1)
424 }
425}
426
427impl<C: LastFmBaseClient> RecentTracksIterator<C> {
428 pub fn new(client: C) -> Self {
432 Self::with_starting_page(client, 1)
433 }
434
435 pub fn with_starting_page(client: C, starting_page: u32) -> Self {
445 let page = std::cmp::max(1, starting_page);
446 Self {
447 client,
448 current_page: page,
449 has_more: true,
450 buffer: Vec::new(),
451 stop_at_timestamp: None,
452 }
453 }
454
455 pub fn with_stop_timestamp(mut self, timestamp: u64) -> Self {
465 self.stop_at_timestamp = Some(timestamp);
466 self
467 }
468}
469
470pub struct ApiRecentTracksIterator<C: LastFmApiClient> {
476 client: C,
477 current_page: u32,
478 has_more: bool,
479 buffer: Vec<Track>,
480 stop_at_timestamp: Option<u64>,
481 total_pages: Option<u32>,
482 from: Option<u64>,
483 to: Option<u64>,
484}
485
486#[async_trait(?Send)]
487impl<C: LastFmApiClient> AsyncPaginatedIterator<Track> for ApiRecentTracksIterator<C> {
488 async fn next(&mut self) -> Result<Option<Track>> {
489 if self.buffer.is_empty() {
490 if !self.has_more {
491 return Ok(None);
492 }
493
494 let page = self
495 .client
496 .api_get_recent_tracks_page_in_range(self.current_page, self.from, self.to)
497 .await?;
498
499 if page.tracks.is_empty() {
500 self.has_more = false;
501 return Ok(None);
502 }
503
504 self.has_more = page.has_next_page;
505 self.total_pages = page.total_pages;
506
507 if let Some(stop_timestamp) = self.stop_at_timestamp {
508 let mut filtered_tracks = Vec::new();
509 for track in page.tracks {
510 if let Some(track_timestamp) = track.timestamp {
511 if track_timestamp <= stop_timestamp {
512 self.has_more = false;
513 break;
514 }
515 }
516 filtered_tracks.push(track);
517 }
518 self.buffer = filtered_tracks;
519 } else {
520 self.buffer = page.tracks;
521 }
522
523 self.buffer.reverse();
524 self.current_page += 1;
525 }
526
527 Ok(self.buffer.pop())
528 }
529
530 fn current_page(&self) -> u32 {
531 self.current_page.saturating_sub(1)
532 }
533
534 fn total_pages(&self) -> Option<u32> {
535 self.total_pages
536 }
537}
538
539impl<C: LastFmApiClient> ApiRecentTracksIterator<C> {
540 pub fn new(client: C) -> Self {
541 Self::with_starting_page(client, 1)
542 }
543
544 pub fn with_starting_page(client: C, starting_page: u32) -> Self {
545 let page = std::cmp::max(1, starting_page);
546 Self {
547 client,
548 current_page: page,
549 has_more: true,
550 buffer: Vec::new(),
551 stop_at_timestamp: None,
552 total_pages: None,
553 from: None,
554 to: None,
555 }
556 }
557
558 pub fn with_range(client: C, from: Option<u64>, to: Option<u64>) -> Self {
568 let mut iterator = Self::new(client);
569 iterator.from = from;
570 iterator.to = to;
571 iterator
572 }
573
574 pub fn with_stop_timestamp(mut self, timestamp: u64) -> Self {
575 self.stop_at_timestamp = Some(timestamp);
576 self
577 }
578}
579
580pub struct AlbumTracksIterator<C: LastFmBaseClient> {
586 client: C,
587 album_name: String,
588 artist_name: String,
589 tracks: Option<Vec<Track>>,
590 index: usize,
591}
592
593#[async_trait(?Send)]
594impl<C: LastFmBaseClient> AsyncPaginatedIterator<Track> for AlbumTracksIterator<C> {
595 async fn next(&mut self) -> Result<Option<Track>> {
596 if self.tracks.is_none() {
598 let tracks_page = self
600 .client
601 .get_album_tracks_page(&self.album_name, &self.artist_name, 1)
602 .await?;
603 log::debug!(
604 "Album '{}' by '{}' has {} tracks: {:?}",
605 self.album_name,
606 self.artist_name,
607 tracks_page.tracks.len(),
608 tracks_page
609 .tracks
610 .iter()
611 .map(|t| &t.name)
612 .collect::<Vec<_>>()
613 );
614
615 if tracks_page.tracks.is_empty() {
616 log::warn!(
617 "🚨 ZERO TRACKS FOUND for album '{}' by '{}' - investigating...",
618 self.album_name,
619 self.artist_name
620 );
621 log::debug!("Full TrackPage for empty album: has_next_page={}, page_number={}, total_pages={:?}",
622 tracks_page.has_next_page, tracks_page.page_number, tracks_page.total_pages);
623 }
624 self.tracks = Some(tracks_page.tracks);
625 }
626
627 if let Some(tracks) = &self.tracks {
629 if self.index < tracks.len() {
630 let track = tracks[self.index].clone();
631 self.index += 1;
632 Ok(Some(track))
633 } else {
634 Ok(None)
635 }
636 } else {
637 Ok(None)
638 }
639 }
640
641 fn current_page(&self) -> u32 {
642 0
644 }
645}
646
647impl<C: LastFmBaseClient> AlbumTracksIterator<C> {
648 pub fn new(client: C, album_name: String, artist_name: String) -> Self {
652 Self {
653 client,
654 album_name,
655 artist_name,
656 tracks: None,
657 index: 0,
658 }
659 }
660}
661
662pub struct SearchTracksIterator<C: LastFmBaseClient> {
667 client: C,
668 query: String,
669 current_page: u32,
670 has_more: bool,
671 buffer: Vec<Track>,
672 total_pages: Option<u32>,
673}
674
675#[async_trait(?Send)]
676impl<C: LastFmBaseClient> AsyncPaginatedIterator<Track> for SearchTracksIterator<C> {
677 async fn next(&mut self) -> Result<Option<Track>> {
678 if self.buffer.is_empty() {
680 if let Some(page) = self.next_page().await? {
681 self.buffer = page.tracks;
682 self.buffer.reverse(); }
684 }
685
686 Ok(self.buffer.pop())
687 }
688
689 fn current_page(&self) -> u32 {
690 self.current_page.saturating_sub(1)
691 }
692
693 fn total_pages(&self) -> Option<u32> {
694 self.total_pages
695 }
696}
697
698impl<C: LastFmBaseClient> SearchTracksIterator<C> {
699 pub fn new(client: C, query: String) -> Self {
703 Self {
704 client,
705 query,
706 current_page: 1,
707 has_more: true,
708 buffer: Vec::new(),
709 total_pages: None,
710 }
711 }
712
713 pub fn with_starting_page(client: C, query: String, starting_page: u32) -> Self {
718 let page = std::cmp::max(1, starting_page);
719 Self {
720 client,
721 query,
722 current_page: page,
723 has_more: true,
724 buffer: Vec::new(),
725 total_pages: None,
726 }
727 }
728
729 pub async fn next_page(&mut self) -> Result<Option<TrackPage>> {
734 if !self.has_more {
735 return Ok(None);
736 }
737
738 let page = self
739 .client
740 .search_tracks_page(&self.query, self.current_page)
741 .await?;
742
743 self.has_more = page.has_next_page;
744 self.current_page += 1;
745 self.total_pages = page.total_pages;
746
747 Ok(Some(page))
748 }
749
750 pub fn total_pages(&self) -> Option<u32> {
754 self.total_pages
755 }
756}
757
758pub struct SearchAlbumsIterator<C: LastFmBaseClient> {
765 client: C,
766 query: String,
767 current_page: u32,
768 has_more: bool,
769 buffer: Vec<Album>,
770 total_pages: Option<u32>,
771}
772
773#[async_trait(?Send)]
774impl<C: LastFmBaseClient> AsyncPaginatedIterator<Album> for SearchAlbumsIterator<C> {
775 async fn next(&mut self) -> Result<Option<Album>> {
776 if self.buffer.is_empty() {
778 if let Some(page) = self.next_page().await? {
779 self.buffer = page.albums;
780 self.buffer.reverse(); }
782 }
783
784 Ok(self.buffer.pop())
785 }
786
787 fn current_page(&self) -> u32 {
788 self.current_page.saturating_sub(1)
789 }
790
791 fn total_pages(&self) -> Option<u32> {
792 self.total_pages
793 }
794}
795
796impl<C: LastFmBaseClient> SearchAlbumsIterator<C> {
797 pub fn new(client: C, query: String) -> Self {
801 Self {
802 client,
803 query,
804 current_page: 1,
805 has_more: true,
806 buffer: Vec::new(),
807 total_pages: None,
808 }
809 }
810
811 pub fn with_starting_page(client: C, query: String, starting_page: u32) -> Self {
816 let page = std::cmp::max(1, starting_page);
817 Self {
818 client,
819 query,
820 current_page: page,
821 has_more: true,
822 buffer: Vec::new(),
823 total_pages: None,
824 }
825 }
826
827 pub async fn next_page(&mut self) -> Result<Option<AlbumPage>> {
832 if !self.has_more {
833 return Ok(None);
834 }
835
836 let page = self
837 .client
838 .search_albums_page(&self.query, self.current_page)
839 .await?;
840
841 self.has_more = page.has_next_page;
842 self.current_page += 1;
843 self.total_pages = page.total_pages;
844
845 Ok(Some(page))
846 }
847
848 pub fn total_pages(&self) -> Option<u32> {
852 self.total_pages
853 }
854}
855
856pub struct SearchArtistsIterator<C: LastFmBaseClient> {
861 client: C,
862 query: String,
863 current_page: u32,
864 has_more: bool,
865 buffer: Vec<crate::Artist>,
866 total_pages: Option<u32>,
867}
868
869#[async_trait(?Send)]
870impl<C: LastFmBaseClient> AsyncPaginatedIterator<crate::Artist> for SearchArtistsIterator<C> {
871 async fn next(&mut self) -> Result<Option<crate::Artist>> {
872 if self.buffer.is_empty() {
874 if let Some(page) = self.next_page().await? {
875 self.buffer = page.artists;
876 self.buffer.reverse(); }
878 }
879
880 Ok(self.buffer.pop())
881 }
882
883 fn current_page(&self) -> u32 {
884 self.current_page.saturating_sub(1)
885 }
886
887 fn total_pages(&self) -> Option<u32> {
888 self.total_pages
889 }
890}
891
892impl<C: LastFmBaseClient> SearchArtistsIterator<C> {
893 pub fn new(client: C, query: String) -> Self {
897 Self {
898 client,
899 query,
900 current_page: 1,
901 has_more: true,
902 buffer: Vec::new(),
903 total_pages: None,
904 }
905 }
906
907 pub fn with_starting_page(client: C, query: String, starting_page: u32) -> Self {
912 let page = std::cmp::max(1, starting_page);
913 Self {
914 client,
915 query,
916 current_page: page,
917 has_more: true,
918 buffer: Vec::new(),
919 total_pages: None,
920 }
921 }
922
923 pub async fn next_page(&mut self) -> Result<Option<crate::ArtistPage>> {
928 if !self.has_more {
929 return Ok(None);
930 }
931
932 let page = self
933 .client
934 .search_artists_page(&self.query, self.current_page)
935 .await?;
936
937 self.has_more = page.has_next_page;
938 self.current_page += 1;
939 self.total_pages = page.total_pages;
940
941 Ok(Some(page))
942 }
943
944 pub fn total_pages(&self) -> Option<u32> {
948 self.total_pages
949 }
950}
951
952pub struct ArtistsIterator<C: LastFmBaseClient> {
962 client: C,
963 current_page: u32,
964 has_more: bool,
965 buffer: Vec<crate::Artist>,
966 total_pages: Option<u32>,
967}
968
969#[async_trait(?Send)]
970impl<C: LastFmBaseClient> AsyncPaginatedIterator<crate::Artist> for ArtistsIterator<C> {
971 async fn next(&mut self) -> Result<Option<crate::Artist>> {
972 if self.buffer.is_empty() {
974 if let Some(page) = self.next_page().await? {
975 self.buffer = page.artists;
976 self.buffer.reverse(); }
978 }
979
980 Ok(self.buffer.pop())
981 }
982
983 fn current_page(&self) -> u32 {
984 self.current_page.saturating_sub(1)
985 }
986
987 fn total_pages(&self) -> Option<u32> {
988 self.total_pages
989 }
990}
991
992impl<C: LastFmBaseClient> ArtistsIterator<C> {
993 pub fn new(client: C) -> Self {
997 Self {
998 client,
999 current_page: 1,
1000 has_more: true,
1001 buffer: Vec::new(),
1002 total_pages: None,
1003 }
1004 }
1005
1006 pub fn with_starting_page(client: C, starting_page: u32) -> Self {
1011 let page = std::cmp::max(1, starting_page);
1012 Self {
1013 client,
1014 current_page: page,
1015 has_more: true,
1016 buffer: Vec::new(),
1017 total_pages: None,
1018 }
1019 }
1020
1021 pub async fn next_page(&mut self) -> Result<Option<crate::ArtistPage>> {
1026 if !self.has_more {
1027 return Ok(None);
1028 }
1029
1030 let page = self.client.get_artists_page(self.current_page).await?;
1031
1032 self.has_more = page.has_next_page;
1033 self.current_page += 1;
1034 self.total_pages = page.total_pages;
1035
1036 Ok(Some(page))
1037 }
1038
1039 pub fn total_pages(&self) -> Option<u32> {
1043 self.total_pages
1044 }
1045}