Skip to main content

lastfm_client/types/
albums.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4use crate::types::{BaseObject, BaseResponse, RankAttr, TrackImage};
5use serde::{Deserialize, Serialize};
6
7use crate::types::utils::u32_from_str;
8
9/// An album from a user's top albums, ranked by play count
10///
11/// Retrieved from the `user.gettopalbums` API endpoint
12#[derive(Serialize, Deserialize, Debug, Clone)]
13#[non_exhaustive]
14pub struct TopAlbum {
15    /// Album name
16    pub name: String,
17    /// Artist information
18    pub artist: BaseObject,
19    /// `MusicBrainz` album identifier (may be empty string)
20    pub mbid: String,
21    /// Last.fm URL for this album
22    pub url: String,
23    /// Total number of times this album has been played
24    #[serde(deserialize_with = "u32_from_str")]
25    pub playcount: u32,
26    /// Album images in various sizes
27    pub image: Vec<TrackImage>,
28    /// Rank attributes (position in top albums)
29    #[serde(rename = "@attr")]
30    pub attr: RankAttr,
31}
32
33impl fmt::Display for TopAlbum {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(
36            f,
37            "#{} - {} by {} ({} plays)",
38            self.attr.rank, self.name, self.artist.name, self.playcount
39        )
40    }
41}
42
43impl PartialEq for TopAlbum {
44    fn eq(&self, other: &Self) -> bool {
45        self.playcount == other.playcount
46    }
47}
48
49impl Eq for TopAlbum {}
50
51impl PartialOrd for TopAlbum {
52    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53        Some(self.cmp(other))
54    }
55}
56
57impl Ord for TopAlbum {
58    fn cmp(&self, other: &Self) -> Ordering {
59        self.playcount.cmp(&other.playcount)
60    }
61}
62
63// SQLITE EXPORT ==============================================================
64
65#[cfg(feature = "sqlite")]
66impl crate::sqlite::SqliteExportable for TopAlbum {
67    fn table_name() -> &'static str {
68        "top_albums"
69    }
70
71    fn create_table_sql() -> &'static str {
72        "CREATE TABLE IF NOT EXISTS top_albums (
73            id        INTEGER PRIMARY KEY AUTOINCREMENT,
74            name      TEXT    NOT NULL,
75            mbid      TEXT    NOT NULL,
76            url       TEXT    NOT NULL,
77            artist    TEXT    NOT NULL,
78            playcount INTEGER NOT NULL,
79            rank      INTEGER NOT NULL
80        )"
81    }
82
83    fn insert_sql() -> &'static str {
84        "INSERT INTO top_albums (name, mbid, url, artist, playcount, rank)
85         VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
86    }
87
88    fn bind_and_execute(&self, stmt: &mut rusqlite::Statement<'_>) -> rusqlite::Result<usize> {
89        let rank: u32 = self.attr.rank.parse().unwrap_or_default();
90        stmt.execute(rusqlite::params![
91            self.name,
92            self.mbid,
93            self.url,
94            self.artist.name,
95            self.playcount,
96            rank,
97        ])
98    }
99}
100
101// SQLITE LOAD ================================================================
102
103#[cfg(feature = "sqlite")]
104impl crate::sqlite::SqliteLoadable for TopAlbum {
105    fn select_sql() -> &'static str {
106        // Columns: name(0) mbid(1) url(2) artist(3) playcount(4) rank(5)
107        "SELECT name, mbid, url, artist, playcount, rank
108         FROM top_albums
109         ORDER BY rank ASC"
110    }
111
112    fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
113        Ok(Self {
114            name: row.get(0)?,
115            mbid: row.get(1)?,
116            url: row.get(2)?,
117            artist: crate::types::BaseObject {
118                name: row.get(3)?,
119                mbid: String::new(),
120                url: String::new(),
121            },
122            playcount: row.get(4)?,
123            attr: crate::types::RankAttr {
124                rank: row.get::<_, u32>(5)?.to_string(),
125            },
126            // Fields not stored in the schema — reconstructed with defaults.
127            image: vec![],
128        })
129    }
130}
131
132/// Top albums response wrapper
133#[derive(Serialize, Deserialize, Debug, Clone)]
134#[non_exhaustive]
135pub struct TopAlbums {
136    /// List of top albums
137    pub album: Vec<TopAlbum>,
138    /// Response metadata
139    #[serde(rename = "@attr")]
140    pub attr: BaseResponse,
141}
142
143/// Top-level top albums API response
144#[derive(Serialize, Deserialize, Debug, Clone)]
145#[non_exhaustive]
146pub struct UserTopAlbums {
147    /// Top albums data
148    pub topalbums: TopAlbums,
149}