Track scanner status for the web API and Subsonic-compatible scan endpoints, add authenticated cover art serving, and wire album artwork into the web UI. Keep Subsonic auth limited to legacy password mode for now so behavior stays honest with the current bcrypt-based user storage.
117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package subsonic
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/benya/temporserv/internal/library"
|
|
"github.com/benya/temporserv/internal/scanner"
|
|
)
|
|
|
|
type Envelope struct {
|
|
SubsonicResponse Response `json:"subsonic-response"`
|
|
}
|
|
|
|
type Response struct {
|
|
Status string `json:"status"`
|
|
Version string `json:"version"`
|
|
Type string `json:"type"`
|
|
Server string `json:"serverVersion"`
|
|
OpenAPI bool `json:"openSubsonic"`
|
|
Artists []ArtistRef `json:"artists,omitempty"`
|
|
RandomSong []SongRef `json:"randomSongs,omitempty"`
|
|
ScanStatus *ScanStatus `json:"scanStatus,omitempty"`
|
|
Error *ErrorRef `json:"error,omitempty"`
|
|
}
|
|
|
|
type ArtistRef struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type SongRef struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Album string `json:"album"`
|
|
Artist string `json:"artist"`
|
|
}
|
|
|
|
type ScanStatus struct {
|
|
Scanning bool `json:"scanning"`
|
|
Count int `json:"count"`
|
|
FolderCount int `json:"folderCount"`
|
|
LastError string `json:"lastError,omitempty"`
|
|
StartedAt string `json:"startedAt,omitempty"`
|
|
FinishedAt string `json:"finishedAt,omitempty"`
|
|
}
|
|
|
|
type ErrorRef struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func PingResponse() Envelope {
|
|
return Envelope{
|
|
SubsonicResponse: Response{
|
|
Status: "ok",
|
|
Version: "1.16.1",
|
|
Type: "temporserv",
|
|
Server: "0.1.0",
|
|
OpenAPI: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func ArtistsResponse(artists []library.Artist) Envelope {
|
|
response := PingResponse()
|
|
for _, artist := range artists {
|
|
response.SubsonicResponse.Artists = append(response.SubsonicResponse.Artists, ArtistRef{
|
|
ID: artist.ID,
|
|
Name: artist.Name,
|
|
})
|
|
}
|
|
return response
|
|
}
|
|
|
|
func RandomSongsResponse(tracks []library.Track) Envelope {
|
|
response := PingResponse()
|
|
for _, track := range tracks {
|
|
response.SubsonicResponse.RandomSong = append(response.SubsonicResponse.RandomSong, SongRef{
|
|
ID: track.ID,
|
|
Title: track.Title,
|
|
Album: track.AlbumTitle,
|
|
Artist: track.ArtistName,
|
|
})
|
|
}
|
|
return response
|
|
}
|
|
|
|
func ScanStatusResponse(status scanner.Status) Envelope {
|
|
response := PingResponse()
|
|
response.SubsonicResponse.ScanStatus = &ScanStatus{
|
|
Scanning: status.Scanning,
|
|
Count: status.Tracks,
|
|
FolderCount: status.Albums,
|
|
LastError: status.LastError,
|
|
StartedAt: formatTime(status.StartedAt),
|
|
FinishedAt: formatTime(status.FinishedAt),
|
|
}
|
|
return response
|
|
}
|
|
|
|
func ErrorResponse(code int, message string) Envelope {
|
|
response := PingResponse()
|
|
response.SubsonicResponse.Status = "failed"
|
|
response.SubsonicResponse.Error = &ErrorRef{
|
|
Code: code,
|
|
Message: message,
|
|
}
|
|
return response
|
|
}
|
|
|
|
func formatTime(value time.Time) string {
|
|
if value.IsZero() {
|
|
return ""
|
|
}
|
|
return value.Format(time.RFC3339)
|
|
}
|