@@ -55,15 +55,14 @@ func makeHTTPMock(query url.Values) (*httptest.ResponseRecorder, *http.Request)
|
||||
func runQueryCases(t *testing.T, contr *Controller, h handlerSubsonic, cases []*queryCase) {
|
||||
t.Helper()
|
||||
for _, qc := range cases {
|
||||
qc := qc // pin
|
||||
t.Run(qc.expectPath, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
rr, req := makeHTTPMock(qc.params)
|
||||
contr.H(h).ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Fatalf("didn't give a 200\n%s", body)
|
||||
}
|
||||
|
||||
goldenPath := makeGoldenPath(t.Name())
|
||||
goldenRegen := os.Getenv("GONIC_REGEN")
|
||||
if goldenRegen == "*" || (goldenRegen != "" && strings.HasPrefix(t.Name(), goldenRegen)) {
|
||||
@@ -86,11 +85,10 @@ func runQueryCases(t *testing.T, contr *Controller, h handlerSubsonic, cases []*
|
||||
diffOpts = append(diffOpts, jd.SET)
|
||||
}
|
||||
diff := expected.Diff(actual, diffOpts...)
|
||||
// pass or fail
|
||||
if len(diff) == 0 {
|
||||
return
|
||||
|
||||
if len(diff) > 0 {
|
||||
t.Errorf("\u001b[31;1mdiffering json\u001b[0m\n%s", diff.Render())
|
||||
}
|
||||
t.Errorf("\u001b[31;1mdiffering json\u001b[0m\n%s", diff.Render())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,27 @@ import (
|
||||
"go.senan.xyz/gonic/server/db"
|
||||
)
|
||||
|
||||
// the subsonic spec metions "artist" a lot when talking about the
|
||||
// the subsonic spec mentions "artist" a lot when talking about the
|
||||
// browse by folder endpoints. but since we're not browsing by tag
|
||||
// we can't access artists. so instead we'll consider the artist of
|
||||
// an track to be the it's respective folder that comes directly
|
||||
// under the root directory
|
||||
|
||||
func (c *Controller) ServeGetIndexes(r *http.Request) *spec.Response {
|
||||
params := r.Context().Value(CtxParams).(params.Params)
|
||||
rootQ := c.DB.
|
||||
Select("id").
|
||||
Model(&db.Album{}).
|
||||
Where("parent_id IS NULL")
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
rootQ = rootQ.
|
||||
Where("root_dir=?", m)
|
||||
}
|
||||
var folders []*db.Album
|
||||
c.DB.
|
||||
Select("*, count(sub.id) child_count").
|
||||
Joins("LEFT JOIN albums sub ON albums.id=sub.parent_id").
|
||||
Where("albums.parent_id=1").
|
||||
Where("albums.parent_id IN ?", rootQ.SubQuery()).
|
||||
Group("albums.id").
|
||||
Order("albums.right_path COLLATE NOCASE").
|
||||
Find(&folders)
|
||||
@@ -31,17 +40,15 @@ func (c *Controller) ServeGetIndexes(r *http.Request) *spec.Response {
|
||||
indexMap := make(map[string]*spec.Index, 27)
|
||||
resp := make([]*spec.Index, 0, 27)
|
||||
for _, folder := range folders {
|
||||
i := lowerUDecOrHash(folder.IndexRightPath())
|
||||
index, ok := indexMap[i]
|
||||
if !ok {
|
||||
index = &spec.Index{
|
||||
Name: i,
|
||||
key := lowerUDecOrHash(folder.IndexRightPath())
|
||||
if _, ok := indexMap[key]; !ok {
|
||||
indexMap[key] = &spec.Index{
|
||||
Name: key,
|
||||
Artists: []*spec.Artist{},
|
||||
}
|
||||
indexMap[i] = index
|
||||
resp = append(resp, index)
|
||||
resp = append(resp, indexMap[key])
|
||||
}
|
||||
index.Artists = append(index.Artists,
|
||||
indexMap[key].Artists = append(indexMap[key].Artists,
|
||||
spec.NewArtistByFolder(folder))
|
||||
}
|
||||
sub := spec.NewResponse()
|
||||
@@ -137,6 +144,10 @@ func (c *Controller) ServeGetAlbumList(r *http.Request) *spec.Response {
|
||||
default:
|
||||
return spec.NewError(10, "unknown value `%s` for parameter 'type'", v)
|
||||
}
|
||||
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("root_dir=?", m)
|
||||
}
|
||||
var folders []*db.Album
|
||||
// TODO: think about removing this extra join to count number
|
||||
// of children. it might make sense to store that in the db
|
||||
@@ -166,50 +177,65 @@ func (c *Controller) ServeSearchTwo(r *http.Request) *spec.Response {
|
||||
return spec.NewError(10, "please provide a `query` parameter")
|
||||
}
|
||||
query = fmt.Sprintf("%%%s%%", strings.TrimSuffix(query, "*"))
|
||||
|
||||
results := &spec.SearchResultTwo{}
|
||||
|
||||
// search "artists"
|
||||
var artists []*db.Album
|
||||
c.DB.
|
||||
Where(`
|
||||
parent_id=1
|
||||
AND ( right_path LIKE ? OR
|
||||
right_path_u_dec LIKE ? )`,
|
||||
query, query).
|
||||
Offset(params.GetOrInt("artistOffset", 0)).
|
||||
Limit(params.GetOrInt("artistCount", 20)).
|
||||
Find(&artists)
|
||||
for _, a := range artists {
|
||||
results.Artists = append(results.Artists,
|
||||
spec.NewDirectoryByFolder(a, nil))
|
||||
rootQ := c.DB.
|
||||
Select("id").
|
||||
Model(&db.Album{}).
|
||||
Where("parent_id IS NULL")
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
rootQ = rootQ.Where("root_dir=?", m)
|
||||
}
|
||||
|
||||
var artists []*db.Album
|
||||
q := c.DB.
|
||||
Where(`parent_id IN ? AND (right_path LIKE ? OR right_path_u_dec LIKE ?)`, rootQ.SubQuery(), query, query).
|
||||
Offset(params.GetOrInt("artistOffset", 0)).
|
||||
Limit(params.GetOrInt("artistCount", 20))
|
||||
if err := q.Find(&artists).Error; err != nil {
|
||||
return spec.NewError(0, "find artists: %v", err)
|
||||
}
|
||||
for _, a := range artists {
|
||||
results.Artists = append(results.Artists, spec.NewDirectoryByFolder(a, nil))
|
||||
}
|
||||
|
||||
// search "albums"
|
||||
var albums []*db.Album
|
||||
c.DB.
|
||||
Where(`
|
||||
tag_artist_id IS NOT NULL
|
||||
AND ( right_path LIKE ? OR
|
||||
right_path_u_dec LIKE ? )`,
|
||||
query, query).
|
||||
q = c.DB.
|
||||
Where(`tag_artist_id IS NOT NULL AND (right_path LIKE ? OR right_path_u_dec LIKE ?)`, query, query).
|
||||
Offset(params.GetOrInt("albumOffset", 0)).
|
||||
Limit(params.GetOrInt("albumCount", 20)).
|
||||
Find(&albums)
|
||||
Limit(params.GetOrInt("albumCount", 20))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&albums).Error; err != nil {
|
||||
return spec.NewError(0, "find albums: %v", err)
|
||||
}
|
||||
for _, a := range albums {
|
||||
results.Albums = append(results.Albums, spec.NewTCAlbumByFolder(a))
|
||||
}
|
||||
|
||||
// search tracks
|
||||
var tracks []*db.Track
|
||||
c.DB.
|
||||
q = c.DB.
|
||||
Preload("Album").
|
||||
Where("filename LIKE ? OR filename_u_dec LIKE ?",
|
||||
query, query).
|
||||
Where("filename LIKE ? OR filename_u_dec LIKE ?", query, query).
|
||||
Offset(params.GetOrInt("songOffset", 0)).
|
||||
Limit(params.GetOrInt("songCount", 20)).
|
||||
Find(&tracks)
|
||||
for _, t := range tracks {
|
||||
results.Tracks = append(results.Tracks,
|
||||
spec.NewTCTrackByFolder(t, t.Album))
|
||||
Limit(params.GetOrInt("songCount", 20))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.
|
||||
Joins("JOIN albums ON albums.id=tracks.album_id").
|
||||
Where("albums.root_dir=?", m)
|
||||
}
|
||||
//
|
||||
if err := q.Find(&tracks).Error; err != nil {
|
||||
return spec.NewError(0, "find tracks: %v", err)
|
||||
}
|
||||
for _, t := range tracks {
|
||||
results.Tracks = append(results.Tracks, spec.NewTCTrackByFolder(t, t.Album))
|
||||
}
|
||||
|
||||
sub := spec.NewResponse()
|
||||
sub.SearchResultTwo = results
|
||||
return sub
|
||||
|
||||
@@ -2,6 +2,7 @@ package ctrlsubsonic
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
@@ -13,6 +14,8 @@ func TestGetIndexes(t *testing.T) {
|
||||
|
||||
runQueryCases(t, contr, contr.ServeGetIndexes, []*queryCase{
|
||||
{url.Values{}, "no_args", false},
|
||||
{url.Values{"musicFolderId": {filepath.Join(m.TmpDir(), "m-0")}}, "with_music_folder_1", false},
|
||||
{url.Values{"musicFolderId": {filepath.Join(m.TmpDir(), "m-1")}}, "with_music_folder_2", false},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,28 +16,32 @@ import (
|
||||
)
|
||||
|
||||
func (c *Controller) ServeGetArtists(r *http.Request) *spec.Response {
|
||||
params := r.Context().Value(CtxParams).(params.Params)
|
||||
var artists []*db.Artist
|
||||
c.DB.
|
||||
q := c.DB.
|
||||
Select("*, count(sub.id) album_count").
|
||||
Joins("LEFT JOIN albums sub ON artists.id=sub.tag_artist_id").
|
||||
Group("artists.id").
|
||||
Order("artists.name COLLATE NOCASE").
|
||||
Find(&artists)
|
||||
Order("artists.name COLLATE NOCASE")
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("sub.root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&artists).Error; err != nil {
|
||||
return spec.NewError(10, "error finding artists: %v", err)
|
||||
}
|
||||
// [a-z#] -> 27
|
||||
indexMap := make(map[string]*spec.Index, 27)
|
||||
resp := make([]*spec.Index, 0, 27)
|
||||
for _, artist := range artists {
|
||||
i := lowerUDecOrHash(artist.IndexName())
|
||||
index, ok := indexMap[i]
|
||||
if !ok {
|
||||
index = &spec.Index{
|
||||
Name: i,
|
||||
key := lowerUDecOrHash(artist.IndexName())
|
||||
if _, ok := indexMap[key]; !ok {
|
||||
indexMap[key] = &spec.Index{
|
||||
Name: key,
|
||||
Artists: []*spec.Artist{},
|
||||
}
|
||||
indexMap[i] = index
|
||||
resp = append(resp, index)
|
||||
resp = append(resp, indexMap[key])
|
||||
}
|
||||
index.Artists = append(index.Artists,
|
||||
indexMap[key].Artists = append(indexMap[key].Artists,
|
||||
spec.NewArtistByTags(artist))
|
||||
}
|
||||
sub := spec.NewResponse()
|
||||
@@ -144,6 +148,9 @@ func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response {
|
||||
default:
|
||||
return spec.NewError(10, "unknown value `%s` for parameter 'type'", listType)
|
||||
}
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("root_dir=?", m)
|
||||
}
|
||||
var albums []*db.Album
|
||||
// TODO: think about removing this extra join to count number
|
||||
// of children. it might make sense to store that in the db
|
||||
@@ -172,47 +179,63 @@ func (c *Controller) ServeSearchThree(r *http.Request) *spec.Response {
|
||||
if err != nil {
|
||||
return spec.NewError(10, "please provide a `query` parameter")
|
||||
}
|
||||
query = fmt.Sprintf("%%%s%%",
|
||||
strings.TrimSuffix(query, "*"))
|
||||
query = fmt.Sprintf("%%%s%%", strings.TrimSuffix(query, "*"))
|
||||
results := &spec.SearchResultThree{}
|
||||
|
||||
// search "artists"
|
||||
var artists []*db.Artist
|
||||
c.DB.
|
||||
Where("name LIKE ? OR name_u_dec LIKE ?",
|
||||
query, query).
|
||||
q := c.DB.
|
||||
Where("name LIKE ? OR name_u_dec LIKE ?", query, query).
|
||||
Offset(params.GetOrInt("artistOffset", 0)).
|
||||
Limit(params.GetOrInt("artistCount", 20)).
|
||||
Find(&artists)
|
||||
for _, a := range artists {
|
||||
results.Artists = append(results.Artists,
|
||||
spec.NewArtistByTags(a))
|
||||
Limit(params.GetOrInt("artistCount", 20))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.
|
||||
Joins("JOIN albums ON albums.tag_artist_id=artists.id").
|
||||
Where("albums.root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&artists).Error; err != nil {
|
||||
return spec.NewError(0, "find artists: %v", err)
|
||||
}
|
||||
for _, a := range artists {
|
||||
results.Artists = append(results.Artists, spec.NewArtistByTags(a))
|
||||
}
|
||||
|
||||
// search "albums"
|
||||
var albums []*db.Album
|
||||
c.DB.
|
||||
q = c.DB.
|
||||
Preload("TagArtist").
|
||||
Where("tag_title LIKE ? OR tag_title_u_dec LIKE ?",
|
||||
query, query).
|
||||
Where("tag_title LIKE ? OR tag_title_u_dec LIKE ?", query, query).
|
||||
Offset(params.GetOrInt("albumOffset", 0)).
|
||||
Limit(params.GetOrInt("albumCount", 20)).
|
||||
Find(&albums)
|
||||
for _, a := range albums {
|
||||
results.Albums = append(results.Albums,
|
||||
spec.NewAlbumByTags(a, a.TagArtist))
|
||||
Limit(params.GetOrInt("albumCount", 20))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&albums).Error; err != nil {
|
||||
return spec.NewError(0, "find albums: %v", err)
|
||||
}
|
||||
for _, a := range albums {
|
||||
results.Albums = append(results.Albums, spec.NewAlbumByTags(a, a.TagArtist))
|
||||
}
|
||||
|
||||
// search tracks
|
||||
var tracks []*db.Track
|
||||
c.DB.
|
||||
q = c.DB.
|
||||
Preload("Album").
|
||||
Where("tag_title LIKE ? OR tag_title_u_dec LIKE ?",
|
||||
query, query).
|
||||
Where("tag_title LIKE ? OR tag_title_u_dec LIKE ?", query, query).
|
||||
Offset(params.GetOrInt("songOffset", 0)).
|
||||
Limit(params.GetOrInt("songCount", 20)).
|
||||
Find(&tracks)
|
||||
for _, t := range tracks {
|
||||
results.Tracks = append(results.Tracks,
|
||||
spec.NewTrackByTags(t, t.Album))
|
||||
Limit(params.GetOrInt("songCount", 20))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.
|
||||
Joins("JOIN albums ON albums.id=tracks.album_id").
|
||||
Where("albums.root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&tracks).Error; err != nil {
|
||||
return spec.NewError(0, "find tracks: %v", err)
|
||||
}
|
||||
for _, t := range tracks {
|
||||
results.Tracks = append(results.Tracks, spec.NewTrackByTags(t, t.Album))
|
||||
}
|
||||
|
||||
sub := spec.NewResponse()
|
||||
sub.SearchResultThree = results
|
||||
return sub
|
||||
@@ -313,17 +336,20 @@ func (c *Controller) ServeGetSongsByGenre(r *http.Request) *spec.Response {
|
||||
if err != nil {
|
||||
return spec.NewError(10, "please provide an `genre` parameter")
|
||||
}
|
||||
// TODO: add musicFolderId parameter
|
||||
// (since 1.12.0) only return albums in the music folder with the given id
|
||||
var tracks []*db.Track
|
||||
c.DB.
|
||||
q := c.DB.
|
||||
Joins("JOIN albums ON tracks.album_id=albums.id").
|
||||
Joins("JOIN track_genres ON track_genres.track_id=tracks.id").
|
||||
Joins("JOIN genres ON track_genres.genre_id=genres.id AND genres.name=?", genre).
|
||||
Preload("Album").
|
||||
Offset(params.GetOrInt("offset", 0)).
|
||||
Limit(params.GetOrInt("count", 10)).
|
||||
Find(&tracks)
|
||||
Limit(params.GetOrInt("count", 10))
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("albums.root_dir=?", m)
|
||||
}
|
||||
if err := q.Find(&tracks).Error; err != nil {
|
||||
return spec.NewError(0, "error finding tracks: %v", err)
|
||||
}
|
||||
sub := spec.NewResponse()
|
||||
sub.TracksByGenre = &spec.TracksByGenre{
|
||||
List: make([]*spec.TrackChild, len(tracks)),
|
||||
|
||||
@@ -2,16 +2,19 @@ package ctrlsubsonic
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetArtists(t *testing.T) {
|
||||
t.Parallel()
|
||||
contr, m := makeController(t)
|
||||
contr, m := makeControllerRoots(t, []string{"m-0", "m-1"})
|
||||
defer m.CleanUp()
|
||||
|
||||
runQueryCases(t, contr, contr.ServeGetArtists, []*queryCase{
|
||||
{url.Values{}, "no_args", false},
|
||||
{url.Values{"musicFolderId": {filepath.Join(m.TmpDir(), "m-0")}}, "with_music_folder_1", false},
|
||||
{url.Values{"musicFolderId": {filepath.Join(m.TmpDir(), "m-1")}}, "with_music_folder_2", false},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
@@ -70,12 +71,22 @@ func (c *Controller) ServeScrobble(r *http.Request) *spec.Response {
|
||||
}
|
||||
|
||||
func (c *Controller) ServeGetMusicFolders(r *http.Request) *spec.Response {
|
||||
folders := &spec.MusicFolders{}
|
||||
folders.List = []*spec.MusicFolder{
|
||||
{ID: 1, Name: "music"},
|
||||
var roots []string
|
||||
err := c.DB.
|
||||
Model(&db.Album{}).
|
||||
Pluck("DISTINCT(root_dir)", &roots).
|
||||
Where("parent_id IS NULL").
|
||||
Error
|
||||
if err != nil {
|
||||
return spec.NewError(0, "error getting roots: %v", err)
|
||||
}
|
||||
|
||||
sub := spec.NewResponse()
|
||||
sub.MusicFolders = folders
|
||||
sub.MusicFolders = &spec.MusicFolders{}
|
||||
sub.MusicFolders.List = make([]*spec.MusicFolder, len(roots))
|
||||
for i, root := range roots {
|
||||
sub.MusicFolders.List[i] = &spec.MusicFolder{ID: root, Name: filepath.Base(root)}
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
@@ -215,6 +226,9 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
|
||||
q = q.Joins("JOIN track_genres ON track_genres.track_id=tracks.id")
|
||||
q = q.Joins("JOIN genres ON genres.id=track_genres.genre_id AND genres.name=?", genre)
|
||||
}
|
||||
if m, _ := params.Get("musicFolderId"); m != "" {
|
||||
q = q.Where("albums.root_dir=?", m)
|
||||
}
|
||||
q.Find(&tracks)
|
||||
sub := spec.NewResponse()
|
||||
sub.RandomTracks = &spec.RandomTracks{}
|
||||
|
||||
@@ -74,10 +74,10 @@ var (
|
||||
errCoverEmpty = errors.New("no cover found for that folder")
|
||||
)
|
||||
|
||||
func coverGetPath(dbc *db.DB, musicPath, podcastPath string, id specid.ID) (string, error) {
|
||||
func coverGetPath(dbc *db.DB, podcastPath string, id specid.ID) (string, error) {
|
||||
switch id.Type {
|
||||
case specid.Album:
|
||||
return coverGetPathAlbum(dbc, musicPath, id.Value)
|
||||
return coverGetPathAlbum(dbc, id.Value)
|
||||
case specid.Podcast:
|
||||
return coverGetPathPodcast(dbc, podcastPath, id.Value)
|
||||
case specid.PodcastEpisode:
|
||||
@@ -87,10 +87,11 @@ func coverGetPath(dbc *db.DB, musicPath, podcastPath string, id specid.ID) (stri
|
||||
}
|
||||
}
|
||||
|
||||
func coverGetPathAlbum(dbc *db.DB, musicPath string, id int) (string, error) {
|
||||
func coverGetPathAlbum(dbc *db.DB, id int) (string, error) {
|
||||
folder := &db.Album{}
|
||||
err := dbc.DB.
|
||||
Select("id, left_path, right_path, cover").
|
||||
Preload("Parent").
|
||||
Select("id, root_dir, left_path, right_path, cover").
|
||||
First(folder, id).
|
||||
Error
|
||||
if err != nil {
|
||||
@@ -100,7 +101,7 @@ func coverGetPathAlbum(dbc *db.DB, musicPath string, id int) (string, error) {
|
||||
return "", errCoverEmpty
|
||||
}
|
||||
return path.Join(
|
||||
musicPath,
|
||||
folder.RootDir,
|
||||
folder.LeftPath,
|
||||
folder.RightPath,
|
||||
folder.Cover,
|
||||
@@ -201,7 +202,7 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
||||
case specid.Track:
|
||||
track, _ := streamGetTrack(c.DB, id.Value)
|
||||
audioFile = track
|
||||
audioPath = path.Join(c.MusicPath, track.RelPath())
|
||||
audioPath = path.Join(track.AbsPath())
|
||||
if err != nil {
|
||||
return spec.NewError(70, "track with id `%s` was not found", id)
|
||||
}
|
||||
@@ -278,7 +279,7 @@ func (c *Controller) ServeDownload(w http.ResponseWriter, r *http.Request) *spec
|
||||
case specid.Track:
|
||||
track, _ := streamGetTrack(c.DB, id.Value)
|
||||
audioFile = track
|
||||
filePath = path.Join(c.MusicPath, track.RelPath())
|
||||
filePath = track.AbsPath()
|
||||
if err != nil {
|
||||
return spec.NewError(70, "track with id `%s` was not found", id)
|
||||
}
|
||||
|
||||
@@ -84,14 +84,10 @@ func NewArtistByFolder(f *db.Album) *Artist {
|
||||
}
|
||||
|
||||
func NewDirectoryByFolder(f *db.Album, children []*TrackChild) *Directory {
|
||||
dir := &Directory{
|
||||
return &Directory{
|
||||
ID: f.SID(),
|
||||
Name: f.RightPath,
|
||||
Children: children,
|
||||
ParentID: f.ParentSID(),
|
||||
}
|
||||
// don't show the root dir as a parent
|
||||
if f.ParentID != 1 {
|
||||
dir.ParentID = f.ParentSID()
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ type MusicFolders struct {
|
||||
}
|
||||
|
||||
type MusicFolder struct {
|
||||
ID int `xml:"id,attr,omitempty" json:"id,omitempty"`
|
||||
ID string `xml:"id,attr,omitempty" json:"id,omitempty"`
|
||||
Name string `xml:"name,attr,omitempty" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -5,32 +5,6 @@
|
||||
"type": "gonic",
|
||||
"albumList": {
|
||||
"album": [
|
||||
{
|
||||
"id": "al-2",
|
||||
"coverArt": "al-2",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-1",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-7",
|
||||
"coverArt": "al-7",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-6",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-13",
|
||||
"coverArt": "al-13",
|
||||
@@ -44,45 +18,6 @@
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-11",
|
||||
"coverArt": "al-11",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-10",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-3",
|
||||
"coverArt": "al-3",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-1",
|
||||
"album": "",
|
||||
"parent": "al-1",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-12",
|
||||
"coverArt": "al-12",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-1",
|
||||
"album": "",
|
||||
"parent": "al-10",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-8",
|
||||
"coverArt": "al-8",
|
||||
@@ -97,11 +32,11 @@
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-4",
|
||||
"coverArt": "al-4",
|
||||
"id": "al-2",
|
||||
"coverArt": "al-2",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-2",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-1",
|
||||
"isDir": true,
|
||||
@@ -109,6 +44,32 @@
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-3",
|
||||
"coverArt": "al-3",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-1",
|
||||
"album": "",
|
||||
"parent": "al-1",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-11",
|
||||
"coverArt": "al-11",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-10",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-9",
|
||||
"coverArt": "al-9",
|
||||
@@ -121,6 +82,45 @@
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-7",
|
||||
"coverArt": "al-7",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-0",
|
||||
"album": "",
|
||||
"parent": "al-6",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-12",
|
||||
"coverArt": "al-12",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-1",
|
||||
"album": "",
|
||||
"parent": "al-10",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"id": "al-4",
|
||||
"coverArt": "al-4",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "album-2",
|
||||
"album": "",
|
||||
"parent": "al-1",
|
||||
"isDir": true,
|
||||
"name": "",
|
||||
"songCount": 3,
|
||||
"duration": 300
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,32 +5,6 @@
|
||||
"type": "gonic",
|
||||
"albumList2": {
|
||||
"album": [
|
||||
{
|
||||
"id": "al-3",
|
||||
"coverArt": "al-3",
|
||||
"artistId": "ar-1",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-11",
|
||||
"coverArt": "al-11",
|
||||
"artistId": "ar-3",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-0",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-4",
|
||||
"coverArt": "al-4",
|
||||
@@ -44,19 +18,6 @@
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-9",
|
||||
"coverArt": "al-9",
|
||||
"artistId": "ar-2",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-2",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-2",
|
||||
"coverArt": "al-2",
|
||||
@@ -70,6 +31,32 @@
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-12",
|
||||
"coverArt": "al-12",
|
||||
"artistId": "ar-3",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-3",
|
||||
"coverArt": "al-3",
|
||||
"artistId": "ar-1",
|
||||
"artist": "artist-0",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-13",
|
||||
"coverArt": "al-13",
|
||||
@@ -83,19 +70,6 @@
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-8",
|
||||
"coverArt": "al-8",
|
||||
"artistId": "ar-2",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-7",
|
||||
"coverArt": "al-7",
|
||||
@@ -110,14 +84,40 @@
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-12",
|
||||
"coverArt": "al-12",
|
||||
"id": "al-8",
|
||||
"coverArt": "al-8",
|
||||
"artistId": "ar-2",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-11",
|
||||
"coverArt": "al-11",
|
||||
"artistId": "ar-3",
|
||||
"artist": "artist-2",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-1",
|
||||
"name": "album-0",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
},
|
||||
{
|
||||
"id": "al-9",
|
||||
"coverArt": "al-9",
|
||||
"artistId": "ar-2",
|
||||
"artist": "artist-1",
|
||||
"created": "2019-11-30T00:00:00Z",
|
||||
"title": "",
|
||||
"album": "",
|
||||
"name": "album-2",
|
||||
"songCount": 3,
|
||||
"duration": 300,
|
||||
"year": 2021
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "ar-1", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "ar-2", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "ar-3", "name": "artist-2", "albumCount": 3 }
|
||||
{ "id": "ar-1", "name": "artist-0", "albumCount": 6 },
|
||||
{ "id": "ar-2", "name": "artist-1", "albumCount": 6 },
|
||||
{ "id": "ar-3", "name": "artist-2", "albumCount": 6 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
20
server/ctrlsubsonic/testdata/test_get_artists_with_music_folder_1
vendored
Normal file
20
server/ctrlsubsonic/testdata/test_get_artists_with_music_folder_1
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"artists": {
|
||||
"ignoredArticles": "",
|
||||
"index": [
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "ar-1", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "ar-2", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "ar-3", "name": "artist-2", "albumCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
server/ctrlsubsonic/testdata/test_get_artists_with_music_folder_2
vendored
Normal file
20
server/ctrlsubsonic/testdata/test_get_artists_with_music_folder_2
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"artists": {
|
||||
"ignoredArticles": "",
|
||||
"index": [
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "ar-1", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "ar-2", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "ar-3", "name": "artist-2", "albumCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,12 @@
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "al-2", "name": "album-0", "albumCount": 0 },
|
||||
{ "id": "al-3", "name": "album-1", "albumCount": 0 },
|
||||
{ "id": "al-4", "name": "album-2", "albumCount": 0 }
|
||||
{ "id": "al-1", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "al-14", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "al-6", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "al-19", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "al-10", "name": "artist-2", "albumCount": 3 },
|
||||
{ "id": "al-23", "name": "artist-2", "albumCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
21
server/ctrlsubsonic/testdata/test_get_indexes_with_music_folder_1
vendored
Normal file
21
server/ctrlsubsonic/testdata/test_get_indexes_with_music_folder_1
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"indexes": {
|
||||
"lastModified": 0,
|
||||
"ignoredArticles": "",
|
||||
"index": [
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "al-1", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "al-6", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "al-10", "name": "artist-2", "albumCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
21
server/ctrlsubsonic/testdata/test_get_indexes_with_music_folder_2
vendored
Normal file
21
server/ctrlsubsonic/testdata/test_get_indexes_with_music_folder_2
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"indexes": {
|
||||
"lastModified": 0,
|
||||
"ignoredArticles": "",
|
||||
"index": [
|
||||
{
|
||||
"name": "a",
|
||||
"artist": [
|
||||
{ "id": "al-14", "name": "artist-0", "albumCount": 3 },
|
||||
{ "id": "al-19", "name": "artist-1", "albumCount": 3 },
|
||||
{ "id": "al-23", "name": "artist-2", "albumCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "gonic",
|
||||
"directory": {
|
||||
"id": "al-3",
|
||||
"parent": "al-1",
|
||||
"name": "album-1",
|
||||
"child": [
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "gonic",
|
||||
"directory": {
|
||||
"id": "al-2",
|
||||
"parent": "al-1",
|
||||
"name": "album-0",
|
||||
"child": [
|
||||
{
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"searchResult2": {
|
||||
"artist": [
|
||||
{ "id": "al-2", "name": "album-0" },
|
||||
{ "id": "al-3", "name": "album-1" },
|
||||
{ "id": "al-4", "name": "album-2" }
|
||||
],
|
||||
"album": [
|
||||
{
|
||||
"id": "al-2",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"type": "gonic",
|
||||
"searchResult2": {}
|
||||
"searchResult2": {
|
||||
"artist": [
|
||||
{ "id": "al-1", "parent": "al-5", "name": "artist-0" },
|
||||
{ "id": "al-6", "parent": "al-5", "name": "artist-1" },
|
||||
{ "id": "al-10", "parent": "al-5", "name": "artist-2" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user