Subsonic: browsing endpoints (getMusicFolders, getIndexes, getMusicDirectory, getGenres, getArtists/Artist, getAlbum, getSong, getVideos/VideoInfo, getArtistInfo/2, getAlbumInfo/2, getSimilarSongs/2, getTopSongs)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
||||
)
|
||||
|
||||
// folderMap resolves between VibeSonic's opaque music-folder IDs and the
|
||||
// small sequential integers the Subsonic protocol expects for
|
||||
// musicFolderId (the official XSD types it as xs:int, and some clients
|
||||
// build folder-picker UIs assuming small integers). The mapping is
|
||||
// derived fresh per request from folder creation order, which is stable
|
||||
// as long as folders aren't deleted/recreated — acceptable for how
|
||||
// rarely music folders change.
|
||||
type folderMap struct {
|
||||
folders []*database.MusicFolder
|
||||
toInt map[string]int
|
||||
toFolder map[int]*database.MusicFolder
|
||||
}
|
||||
|
||||
func loadFolderMap(db *sql.DB) (*folderMap, error) {
|
||||
folders, err := database.ListMusicFolders(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fm := &folderMap{
|
||||
folders: folders,
|
||||
toInt: make(map[string]int, len(folders)),
|
||||
toFolder: make(map[int]*database.MusicFolder, len(folders)),
|
||||
}
|
||||
for i, f := range folders {
|
||||
id := i + 1
|
||||
fm.toInt[f.ID] = id
|
||||
fm.toFolder[id] = f
|
||||
}
|
||||
return fm, nil
|
||||
}
|
||||
|
||||
func (fm *folderMap) IntID(folderID string) string {
|
||||
return strconv.Itoa(fm.toInt[folderID])
|
||||
}
|
||||
|
||||
func (fm *folderMap) ByIntID(s string) *database.MusicFolder {
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fm.toFolder[n]
|
||||
}
|
||||
|
||||
// Allowed returns the folders a user may see: every folder, unless the
|
||||
// user has an explicit restriction list configured.
|
||||
func (fm *folderMap) Allowed(db *sql.DB, user *database.User) ([]*database.MusicFolder, error) {
|
||||
allowedIDs, err := database.AllowedMusicFolderIDs(db, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(allowedIDs) == 0 {
|
||||
return fm.folders, nil
|
||||
}
|
||||
set := make(map[string]bool, len(allowedIDs))
|
||||
for _, id := range allowedIDs {
|
||||
set[id] = true
|
||||
}
|
||||
var out []*database.MusicFolder
|
||||
for _, f := range fm.folders {
|
||||
if set[f.ID] {
|
||||
out = append(out, f)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// UserCanSeeFolder reports whether user is allowed to see folderID,
|
||||
// given their restriction list (empty = unrestricted).
|
||||
func UserCanSeeFolder(db *sql.DB, user *database.User, folderID string) (bool, error) {
|
||||
allowedIDs, err := database.AllowedMusicFolderIDs(db, user.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(allowedIDs) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
for _, id := range allowedIDs {
|
||||
if id == folderID {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// UserCanSeeMediaFile reports whether user is allowed to see a song
|
||||
// belonging to the given music folder (per their folder restriction).
|
||||
func UserCanSeeMediaFile(db *sql.DB, user *database.User, mediaFile *database.MediaFile) (bool, error) {
|
||||
return UserCanSeeFolder(db, user, mediaFile.MusicFolderID)
|
||||
}
|
||||
Reference in New Issue
Block a user