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/// Top albums response wrapper
102#[derive(Serialize, Deserialize, Debug, Clone)]
103#[non_exhaustive]
104pub struct TopAlbums {
105    /// List of top albums
106    pub album: Vec<TopAlbum>,
107    /// Response metadata
108    #[serde(rename = "@attr")]
109    pub attr: BaseResponse,
110}
111
112/// Top-level top albums API response
113#[derive(Serialize, Deserialize, Debug, Clone)]
114#[non_exhaustive]
115pub struct UserTopAlbums {
116    /// Top albums data
117    pub topalbums: TopAlbums,
118}