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 tracks from a specific album.
154 fn album_tracks(&self, album_name: &str, artist_name: &str) -> crate::AlbumTracksIterator;
155
156 /// Create an iterator for browsing the user's recent tracks/scrobbles.
157 fn recent_tracks(&self) -> crate::RecentTracksIterator;
158
159 /// Create an iterator for browsing the user's recent tracks starting from a specific page.
160 fn recent_tracks_from_page(&self, starting_page: u32) -> crate::RecentTracksIterator;
161}