import Foundation // MARK: - Server Configuration struct ServerConfig: Codable, Identifiable, Hashable { var id: UUID = UUID() var name: String var url: String var username: String var password: String // stored in Keychain in production var isActive: Bool = true var baseURL: URL? { URL(string: url) } func hash(into hasher: inout Hasher) { hasher.combine(id) } } // MARK: - Subsonic API Response Wrappers struct SubsonicResponse: Codable { let subsonicResponse: SubsonicResponseBody enum CodingKeys: String, CodingKey { case subsonicResponse = "subsonic-response" } } struct SubsonicResponseBody: Codable { let status: String let version: String? let type: String? let serverVersion: String? let openSubsonic: Bool? let error: SubsonicError? // Various response types let musicFolders: MusicFoldersContainer? let indexes: IndexesContainer? let artists: ArtistsContainer? let artist: ArtistWithAlbums? let album: AlbumWithSongs? let song: Song? let directory: DirectoryContainer? let searchResult3: SearchResult3? let albumList2: AlbumList2Container? let playlists: PlaylistsContainer? let playlist: PlaylistWithSongs? let genres: GenresContainer? let starred2: Starred2Container? let nowPlaying: NowPlayingContainer? let randomSongs: RandomSongsContainer? let scanStatus: ScanStatus? let lyrics: LyricsResult? let internetRadioStations: InternetRadioContainer? let similarSongs2: SimilarSongsContainer? } struct SubsonicError: Codable { let code: Int let message: String } // MARK: - Music Folders struct MusicFoldersContainer: Codable { let musicFolder: [MusicFolder]? } struct MusicFolder: Codable, Identifiable { let id: String let name: String? } // MARK: - Indexes / Artists struct IndexesContainer: Codable { let index: [ArtistIndex]? let lastModified: Int64? let ignoredArticles: String? } struct ArtistsContainer: Codable { let index: [ArtistIndex]? let ignoredArticles: String? } struct ArtistIndex: Codable, Identifiable { let name: String let artist: [Artist]? var id: String { name } } struct Artist: Codable, Identifiable { let id: String let name: String let coverArt: String? let artistImageUrl: String? let albumCount: Int? let starred: String? let musicBrainzId: String? let sortName: String? } // MARK: - Albums struct ArtistWithAlbums: Codable, Identifiable { let id: String let name: String let coverArt: String? let albumCount: Int? let album: [Album]? } struct Album: Codable, Identifiable { let id: String let name: String let artist: String? let artistId: String? let coverArt: String? let songCount: Int? let duration: Int? let playCount: Int? let created: String? let starred: String? let year: Int? let genre: String? } struct AlbumWithSongs: Codable, Identifiable { let id: String let name: String let artist: String? let artistId: String? let coverArt: String? let songCount: Int? let duration: Int? let playCount: Int? let created: String? let starred: String? let year: Int? let genre: String? let song: [Song]? } struct AlbumList2Container: Codable { let album: [Album]? } // MARK: - Songs struct Song: Codable, Identifiable { let id: String let parent: String? let isDir: Bool? let title: String let album: String? let artist: String? let track: Int? let year: Int? let genre: String? let coverArt: String? let size: Int64? let contentType: String? let suffix: String? let transcodedContentType: String? let transcodedSuffix: String? let duration: Int? let bitRate: Int? let path: String? let playCount: Int? let discNumber: Int? let created: String? let albumId: String? let artistId: String? let type: String? let starred: String? let bpm: Int? let musicBrainzId: String? var durationFormatted: String { guard let dur = duration else { return "--:--" } let min = dur / 60 let sec = dur % 60 return String(format: "%d:%02d", min, sec) } } // MARK: - Directory struct DirectoryContainer: Codable { let id: String let name: String? let parent: String? let child: [Song]? } // MARK: - Search struct SearchResult3: Codable { let artist: [Artist]? let album: [Album]? let song: [Song]? } // MARK: - Playlists struct PlaylistsContainer: Codable { let playlist: [Playlist]? } struct Playlist: Codable, Identifiable { let id: String let name: String let comment: String? let songCount: Int? let duration: Int? let coverArt: String? let owner: String? let `public`: Bool? let created: String? let changed: String? } struct PlaylistWithSongs: Codable, Identifiable { let id: String let name: String let comment: String? let songCount: Int? let duration: Int? let coverArt: String? let owner: String? let `public`: Bool? let created: String? let changed: String? let entry: [Song]? } // MARK: - Genres struct GenresContainer: Codable { let genre: [Genre]? } struct Genre: Codable, Identifiable { let value: String let songCount: Int? let albumCount: Int? var id: String { value } enum CodingKeys: String, CodingKey { case value, songCount, albumCount } } // MARK: - Starred struct Starred2Container: Codable { let artist: [Artist]? let album: [Album]? let song: [Song]? } // MARK: - Now Playing struct NowPlayingContainer: Codable { let entry: [NowPlayingEntry]? } struct NowPlayingEntry: Codable, Identifiable { let id: String let title: String let artist: String? let album: String? let username: String? let minutesAgo: Int? let playerId: Int? let playerName: String? } // MARK: - Random Songs struct RandomSongsContainer: Codable { let song: [Song]? } // MARK: - Scan Status struct ScanStatus: Codable { let scanning: Bool let count: Int? } // MARK: - Lyrics struct LyricsResult: Codable { let artist: String? let title: String? let value: String? } // MARK: - Offline Download struct DownloadedSong: Codable, Identifiable { let id: String let serverId: UUID let song: Song let localPath: String let downloadDate: Date let fileSize: Int64 } // MARK: - Internet Radio struct InternetRadioContainer: Codable { let internetRadioStation: [RadioStation]? } struct RadioStation: Codable, Identifiable { let id: String let name: String let streamUrl: String let homePageUrl: String? enum CodingKeys: String, CodingKey { case id, name, streamUrl, homePageUrl } } // MARK: - Similar Songs struct SimilarSongsContainer: Codable { let song: [Song]? }