lastfm_edit/
trait.rs

1use crate::edit::ExactScrobbleEdit;
2use crate::session::LastFmEditSession;
3use crate::{AlbumPage, EditResponse, LastFmError, Result, ScrobbleEdit, Track, TrackPage};
4use async_trait::async_trait;
5
6/// Trait for Last.fm client operations that can be mocked for testing.
7///
8/// This trait abstracts the core functionality needed for Last.fm scrobble editing
9/// to enable easy mocking and testing. All methods that perform network operations or
10/// state changes are included to support comprehensive test coverage.
11///
12/// # Mocking Support
13///
14/// When the `mock` feature is enabled, this crate provides `MockLastFmEditClient`
15/// that implements this trait using the `mockall` library.
16///
17#[cfg_attr(feature = "mock", mockall::automock)]
18#[async_trait(?Send)]
19pub trait LastFmEditClient {
20    /// Authenticate with Last.fm using username and password.
21    async fn login(&self, username: &str, password: &str) -> Result<()>;
22
23    /// Get the currently authenticated username.
24    fn username(&self) -> String;
25
26    /// Check if the client is currently authenticated.
27    fn is_logged_in(&self) -> bool;
28
29    /// Fetch recent scrobbles from the user's listening history.
30    async fn get_recent_scrobbles(&self, page: u32) -> Result<Vec<Track>>;
31
32    /// Find a scrobble by its timestamp in recent scrobbles.
33    async fn find_scrobble_by_timestamp(&self, timestamp: u64) -> Result<Track> {
34        log::debug!("Searching for scrobble with timestamp {timestamp}");
35
36        // Search through recent scrobbles to find the one with matching timestamp
37        for page in 1..=10 {
38            // Search up to 10 pages of recent scrobbles
39            let scrobbles = self.get_recent_scrobbles(page).await?;
40
41            for scrobble in scrobbles {
42                if let Some(scrobble_timestamp) = scrobble.timestamp {
43                    if scrobble_timestamp == timestamp {
44                        log::debug!(
45                            "Found scrobble: '{}' by '{}' with album: '{:?}', album_artist: '{:?}'",
46                            scrobble.name,
47                            scrobble.artist,
48                            scrobble.album,
49                            scrobble.album_artist
50                        );
51                        return Ok(scrobble);
52                    }
53                }
54            }
55        }
56
57        Err(LastFmError::Parse(format!(
58            "Could not find scrobble with timestamp {timestamp}"
59        )))
60    }
61
62    /// Find the most recent scrobble for a specific track.
63    async fn find_recent_scrobble_for_track(
64        &self,
65        track_name: &str,
66        artist_name: &str,
67        max_pages: u32,
68    ) -> Result<Option<Track>>;
69
70    /// Edit a scrobble with the given edit parameters.
71    async fn edit_scrobble(&self, edit: &ScrobbleEdit) -> Result<EditResponse>;
72
73    /// Edit a single scrobble with complete information.
74    ///
75    /// This method performs a single edit operation on a fully-specified scrobble.
76    /// Unlike `edit_scrobble`, it does not perform enrichment or multiple edits.
77    async fn edit_scrobble_single(
78        &self,
79        exact_edit: &ExactScrobbleEdit,
80        max_retries: u32,
81    ) -> Result<EditResponse>;
82
83    /// Discover all scrobble edit variations based on the provided ScrobbleEdit template.
84    ///
85    /// This method analyzes what fields are specified in the input ScrobbleEdit and discovers
86    /// all relevant scrobble instances that match the criteria:
87    /// - If track_name_original is specified: discovers all album variations of that track
88    /// - If only album_name_original is specified: discovers all tracks in that album  
89    /// - If neither is specified: discovers all tracks by that artist
90    ///
91    /// Returns fully-specified ExactScrobbleEdit instances with all metadata populated
92    /// from the user's library, ready for editing operations.
93    async fn discover_scrobble_edit_variations(
94        &self,
95        edit: &ScrobbleEdit,
96    ) -> Result<Vec<ExactScrobbleEdit>>;
97
98    /// Get tracks from a specific album page.
99    async fn get_album_tracks(&self, album_name: &str, artist_name: &str) -> Result<Vec<Track>>;
100
101    /// Edit album metadata by updating scrobbles with new album name.
102    async fn edit_album(
103        &self,
104        old_album_name: &str,
105        new_album_name: &str,
106        artist_name: &str,
107    ) -> Result<EditResponse>;
108
109    /// Edit artist metadata by updating scrobbles with new artist name.
110    async fn edit_artist(
111        &self,
112        old_artist_name: &str,
113        new_artist_name: &str,
114    ) -> Result<EditResponse>;
115
116    /// Edit artist metadata for a specific track only.
117    async fn edit_artist_for_track(
118        &self,
119        track_name: &str,
120        old_artist_name: &str,
121        new_artist_name: &str,
122    ) -> Result<EditResponse>;
123
124    /// Edit artist metadata for all tracks in a specific album.
125    async fn edit_artist_for_album(
126        &self,
127        album_name: &str,
128        old_artist_name: &str,
129        new_artist_name: &str,
130    ) -> Result<EditResponse>;
131
132    /// Get a page of tracks from the user's library for the specified artist.
133    async fn get_artist_tracks_page(&self, artist: &str, page: u32) -> Result<TrackPage>;
134
135    /// Get a page of albums from the user's library for the specified artist.
136    async fn get_artist_albums_page(&self, artist: &str, page: u32) -> Result<AlbumPage>;
137
138    /// Get a page of tracks from the user's recent listening history.
139    async fn get_recent_tracks_page(&self, page: u32) -> Result<TrackPage>;
140
141    /// Extract the current session state for persistence.
142    fn get_session(&self) -> LastFmEditSession;
143
144    /// Restore session state from a previously saved session.
145    fn restore_session(&self, session: LastFmEditSession);
146
147    /// Create an iterator for browsing an artist's tracks from the user's library.
148    fn artist_tracks(&self, artist: &str) -> crate::ArtistTracksIterator;
149
150    /// Create an iterator for browsing an artist's albums from the user's library.
151    fn artist_albums(&self, artist: &str) -> crate::ArtistAlbumsIterator;
152
153    /// Create an iterator for browsing the user's recent tracks/scrobbles.
154    fn recent_tracks(&self) -> crate::RecentTracksIterator;
155
156    /// Create an iterator for browsing the user's recent tracks starting from a specific page.
157    fn recent_tracks_from_page(&self, starting_page: u32) -> crate::RecentTracksIterator;
158}