Files
ferrosonic/src/subsonic/models.rs
Jamie Hewitt 766614f5e9 Remove dead code and #![allow(dead_code)] blanket suppressions
- Delete src/audio/queue.rs (321 lines, PlayQueue never used)
- Remove #![allow(dead_code)] from audio, subsonic, and mpris module roots
- Remove unused MpvEvent2 enum, playlist_clear, get_volume, get_path,
  is_eof, observe_property from mpv.rs
- Remove unused DEFAULT_DEVICE_ID, is_available, get_effective_rate
  from pipewire.rs (and associated dead test)
- Remove unused search(), get_cover_art_url() from subsonic client
- Remove unused SearchResult3Data, SearchResult3 model structs
- Move parse_song_id_from_url into #[cfg(test)] block (only used by tests)
- Add targeted #[allow(dead_code)] on deserialization-only fields
  (MpvEvent, SubsonicResponseInner.version, ArtistIndex.name)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 23:56:52 +00:00

222 lines
5.3 KiB
Rust

//! Subsonic API response models
use serde::{Deserialize, Serialize};
/// Wrapper for all Subsonic API responses
#[derive(Debug, Deserialize)]
pub struct SubsonicResponse<T> {
#[serde(rename = "subsonic-response")]
pub subsonic_response: SubsonicResponseInner<T>,
}
#[derive(Debug, Deserialize)]
pub struct SubsonicResponseInner<T> {
pub status: String,
#[allow(dead_code)] // Present in API response, needed for deserialization
pub version: String,
#[serde(default)]
pub error: Option<ApiError>,
#[serde(flatten)]
pub data: Option<T>,
}
/// API error response
#[derive(Debug, Deserialize)]
pub struct ApiError {
pub code: i32,
pub message: String,
}
/// Artists response wrapper
#[derive(Debug, Deserialize)]
pub struct ArtistsData {
pub artists: ArtistsIndex,
}
#[derive(Debug, Deserialize)]
pub struct ArtistsIndex {
#[serde(default)]
pub index: Vec<ArtistIndex>,
}
#[derive(Debug, Deserialize)]
pub struct ArtistIndex {
#[allow(dead_code)] // Present in API response, needed for deserialization
pub name: String,
#[serde(default)]
pub artist: Vec<Artist>,
}
/// Artist
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artist {
pub id: String,
pub name: String,
#[serde(default, rename = "albumCount")]
pub album_count: Option<i32>,
#[serde(default, rename = "coverArt")]
pub cover_art: Option<String>,
}
/// Artist detail with albums
#[derive(Debug, Deserialize)]
pub struct ArtistData {
pub artist: ArtistDetail,
}
#[derive(Debug, Deserialize)]
pub struct ArtistDetail {
pub id: String,
pub name: String,
#[serde(default)]
pub album: Vec<Album>,
}
/// Album
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Album {
pub id: String,
pub name: String,
#[serde(default)]
pub artist: Option<String>,
#[serde(default, rename = "artistId")]
pub artist_id: Option<String>,
#[serde(default, rename = "coverArt")]
pub cover_art: Option<String>,
#[serde(default, rename = "songCount")]
pub song_count: Option<i32>,
#[serde(default)]
pub duration: Option<i32>,
#[serde(default)]
pub year: Option<i32>,
#[serde(default)]
pub genre: Option<String>,
}
/// Album detail with songs
#[derive(Debug, Deserialize)]
pub struct AlbumData {
pub album: AlbumDetail,
}
#[derive(Debug, Deserialize)]
pub struct AlbumDetail {
pub id: String,
pub name: String,
#[serde(default)]
pub artist: Option<String>,
#[serde(default, rename = "artistId")]
pub artist_id: Option<String>,
#[serde(default)]
pub year: Option<i32>,
#[serde(default)]
pub song: Vec<Child>,
}
/// Song/Media item (called "Child" in Subsonic API)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Child {
pub id: String,
#[serde(default)]
pub parent: Option<String>,
#[serde(default, rename = "isDir")]
pub is_dir: bool,
pub title: String,
#[serde(default)]
pub album: Option<String>,
#[serde(default)]
pub artist: Option<String>,
#[serde(default)]
pub track: Option<i32>,
#[serde(default)]
pub year: Option<i32>,
#[serde(default)]
pub genre: Option<String>,
#[serde(default, rename = "coverArt")]
pub cover_art: Option<String>,
#[serde(default)]
pub size: Option<i64>,
#[serde(default, rename = "contentType")]
pub content_type: Option<String>,
#[serde(default)]
pub suffix: Option<String>,
#[serde(default)]
pub duration: Option<i32>,
#[serde(default, rename = "bitRate")]
pub bit_rate: Option<i32>,
#[serde(default)]
pub path: Option<String>,
#[serde(default, rename = "discNumber")]
pub disc_number: Option<i32>,
}
impl Child {
/// Format duration as MM:SS
pub fn format_duration(&self) -> String {
match self.duration {
Some(d) => {
let mins = d / 60;
let secs = d % 60;
format!("{:02}:{:02}", mins, secs)
}
None => "--:--".to_string(),
}
}
}
/// Playlists response
#[derive(Debug, Deserialize)]
pub struct PlaylistsData {
pub playlists: PlaylistsInner,
}
#[derive(Debug, Deserialize)]
pub struct PlaylistsInner {
#[serde(default)]
pub playlist: Vec<Playlist>,
}
/// Playlist
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Playlist {
pub id: String,
pub name: String,
#[serde(default)]
pub owner: Option<String>,
#[serde(default, rename = "songCount")]
pub song_count: Option<i32>,
#[serde(default)]
pub duration: Option<i32>,
#[serde(default, rename = "coverArt")]
pub cover_art: Option<String>,
#[serde(default)]
pub public: Option<bool>,
#[serde(default)]
pub comment: Option<String>,
}
/// Playlist detail with songs
#[derive(Debug, Deserialize)]
pub struct PlaylistData {
pub playlist: PlaylistDetail,
}
#[derive(Debug, Deserialize)]
pub struct PlaylistDetail {
pub id: String,
pub name: String,
#[serde(default)]
pub owner: Option<String>,
#[serde(default, rename = "songCount")]
pub song_count: Option<i32>,
#[serde(default)]
pub duration: Option<i32>,
#[serde(default)]
pub entry: Vec<Child>,
}
/// Ping response (for testing connection)
#[derive(Debug, Deserialize)]
pub struct PingData {}