lastfm_edit/discovery/
artist_tracks.rs

1use super::common::filter_by_original_album_artist;
2use crate::{
3    AsyncDiscoveryIterator, AsyncPaginatedIterator, ExactScrobbleEdit, LastFmEditClientImpl,
4    Result, ScrobbleEdit,
5};
6use async_trait::async_trait;
7
8/// Case 4: Artist tracks discovery (neither track nor album specified)
9///
10/// This discovers all tracks by an artist by iterating through the artist's catalog
11/// and for each track, loading its scrobbles incrementally. This is the most complex
12/// case as it involves nested iteration.
13pub struct ArtistTracksDiscovery {
14    client: LastFmEditClientImpl,
15    edit: ScrobbleEdit,
16    tracks_iterator: crate::ArtistTracksIterator,
17    current_track_results: Vec<ExactScrobbleEdit>,
18    current_track_index: usize,
19}
20
21impl ArtistTracksDiscovery {
22    pub fn new(client: LastFmEditClientImpl, edit: ScrobbleEdit) -> Self {
23        let tracks_iterator =
24            crate::ArtistTracksIterator::new(client.clone(), edit.artist_name_original.clone());
25
26        Self {
27            client,
28            edit,
29            tracks_iterator,
30            current_track_results: Vec::new(),
31            current_track_index: 0,
32        }
33    }
34}
35
36#[async_trait(?Send)]
37impl AsyncDiscoveryIterator<ExactScrobbleEdit> for ArtistTracksDiscovery {
38    async fn next(&mut self) -> Result<Option<ExactScrobbleEdit>> {
39        // If we have results from the current track, return the next one
40        if self.current_track_index < self.current_track_results.len() {
41            let result = self.current_track_results[self.current_track_index].clone();
42            self.current_track_index += 1;
43            return Ok(Some(result));
44        }
45
46        // Get the next track from the iterator
47        while let Some(track) = self.tracks_iterator.next().await? {
48            log::debug!(
49                "Getting scrobble data for track '{}' by '{}'",
50                track.name,
51                self.edit.artist_name_original
52            );
53
54            // Get scrobble data for this track
55            match self
56                .client
57                .load_edit_form_values_internal(&track.name, &self.edit.artist_name_original)
58                .await
59            {
60                Ok(track_scrobbles) => {
61                    // Apply user's changes and filtering
62                    let mut modified_edits = Vec::new();
63                    for scrobble in track_scrobbles {
64                        let mut modified_edit = scrobble.clone();
65                        if let Some(new_track_name) = &self.edit.track_name {
66                            modified_edit.track_name = new_track_name.clone();
67                        }
68                        if let Some(new_album_name) = &self.edit.album_name {
69                            modified_edit.album_name = new_album_name.clone();
70                        }
71                        modified_edit.artist_name = self.edit.artist_name.clone();
72                        if let Some(new_album_artist_name) = &self.edit.album_artist_name {
73                            modified_edit.album_artist_name = new_album_artist_name.clone();
74                        }
75                        modified_edit.edit_all = self.edit.edit_all;
76                        modified_edits.push(modified_edit);
77                    }
78
79                    let filtered_edits =
80                        filter_by_original_album_artist(modified_edits, &self.edit);
81
82                    if !filtered_edits.is_empty() {
83                        // Store results and return the first one
84                        self.current_track_results = filtered_edits;
85                        self.current_track_index = 1; // We'll return the first result below
86                        return Ok(Some(self.current_track_results[0].clone()));
87                    }
88                }
89                Err(e) => {
90                    log::debug!(
91                        "Failed to get scrobble data for track '{}': {}",
92                        track.name,
93                        e
94                    );
95                    // Continue with next track
96                }
97            }
98        }
99
100        // No more tracks
101        Ok(None)
102    }
103}