lastfm_edit/discovery/
track_variations.rs

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