refactor
This commit is contained in:
32
.golangci.yml
Normal file
32
.golangci.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
||||||
|
- errcheck
|
||||||
|
- dupl
|
||||||
|
- gosec
|
||||||
|
- text: "weak cryptographic primitive"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- path: model/model.go
|
||||||
|
linters:
|
||||||
|
- lll
|
||||||
|
- path: server/handler/
|
||||||
|
source: "next http.HandlerFunc"
|
||||||
|
linters:
|
||||||
|
- interfacer
|
||||||
|
- path: server/handler/
|
||||||
|
source: "session.Save"
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- path: server/handler/
|
||||||
|
source: "w.Write"
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
programName = "gonic"
|
programName = "gonic"
|
||||||
programVar = "GONIC"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ func main() {
|
|||||||
*musicPath,
|
*musicPath,
|
||||||
*listenAddr,
|
*listenAddr,
|
||||||
)
|
)
|
||||||
|
s.SetupAdmin()
|
||||||
|
s.SetupSubsonic()
|
||||||
log.Printf("starting server at %s", *listenAddr)
|
log.Printf("starting server at %s", *listenAddr)
|
||||||
if err := s.ListenAndServe(); err != nil {
|
if err := s.ListenAndServe(); err != nil {
|
||||||
log.Fatalf("error starting server: %v\n", err)
|
log.Fatalf("error starting server: %v\n", err)
|
||||||
|
|||||||
29
gen_handler_tests
Executable file
29
gen_handler_tests
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# by folder
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=alphabeticalByArtist" | jq > server/handler/test_data/test_get_album_list_alpha_artist
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=alphabeticalByName" | jq > server/handler/test_data/test_get_album_list_alpha_name
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=alphabeticalByName" | jq > server/handler/test_data/test_get_album_list_two_alpha_name
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=newest" | jq > server/handler/test_data/test_get_album_list_newest
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=random" | jq > server/handler/test_data/test_get_album_list_random
|
||||||
|
curl "http://localhost:6969/rest/search2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=13" | jq > server/handler/test_data/test_search_two_q_13
|
||||||
|
curl "http://localhost:6969/rest/search2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=ani" | jq > server/handler/test_data/test_search_two_q_ani
|
||||||
|
curl "http://localhost:6969/rest/search2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=cert" | jq > server/handler/test_data/test_search_two_q_cert
|
||||||
|
curl 'http://localhost:6969/rest/getIndexes.view?c=Jamstash&p=admin&u=admin&v=1.9.0&f=json' | jq > server/handler/test_data/test_get_indexes_no_args
|
||||||
|
curl 'http://localhost:6969/rest/getMusicDirectory.view?c=Jamsstash&id=2&p=admin&u=admin&v=1.9.0&f=json' | jq > server/handler/test_data/test_get_music_directory_without_tracks
|
||||||
|
curl 'http://localhost:6969/rest/getMusicDirectory.view?c=Jamsstash&id=3&p=admin&u=admin&v=1.9.0&f=json' | jq > server/handler/test_data/test_get_music_directory_with_tracks
|
||||||
|
|
||||||
|
# by tags
|
||||||
|
curl "http://localhost:6969/rest/getAlbum.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&id=2" | jq > server/handler/test_data/test_get_album_without_cover
|
||||||
|
curl "http://localhost:6969/rest/getAlbum.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&id=3" | jq > server/handler/test_data/test_get_album_with_cover
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=alphabeticalByArtist" | jq > server/handler/test_data/test_get_album_list_two_alpha_artist
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=alphabeticalByName" | jq > server/handler/test_data/test_get_album_list_two_alpha_name
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=newest" | jq > server/handler/test_data/test_get_album_list_two_newest
|
||||||
|
curl "http://localhost:6969/rest/getAlbumList2.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&type=random" | jq > server/handler/test_data/test_get_album_list_two_random
|
||||||
|
curl "http://localhost:6969/rest/getArtist.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&id=1" | jq > server/handler/test_data/test_get_artist_id_one
|
||||||
|
curl "http://localhost:6969/rest/getArtist.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&id=2" | jq > server/handler/test_data/test_get_artist_id_two
|
||||||
|
curl "http://localhost:6969/rest/getArtist.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&id=3" | jq > server/handler/test_data/test_get_artist_id_three
|
||||||
|
curl "http://localhost:6969/rest/getArtists.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0" | jq > server/handler/test_data/test_get_artists_no_args
|
||||||
|
curl "http://localhost:6969/rest/search3.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=13" | jq > server/handler/test_data/test_search_three_q_13
|
||||||
|
curl "http://localhost:6969/rest/search3.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=ani" | jq > server/handler/test_data/test_search_three_q_ani
|
||||||
|
curl "http://localhost:6969/rest/search3.view?c=Jamstash&f=json&p=admin&u=admin&v=1.9.0&query=cert" | jq > server/handler/test_data/test_search_three_q_cert
|
||||||
9
mime/mime.go
Normal file
9
mime/mime.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package mime
|
||||||
|
|
||||||
|
var Types = map[string]string{
|
||||||
|
"mp3": "audio/mpeg",
|
||||||
|
"flac": "audio/x-flac",
|
||||||
|
"aac": "audio/x-aac",
|
||||||
|
"m4a": "audio/m4a",
|
||||||
|
"ogg": "audio/ogg",
|
||||||
|
}
|
||||||
@@ -1,31 +1,26 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
// q: what in tarnation are the `IsNew`s for?
|
"github.com/sentriz/gonic/mime"
|
||||||
// a: it's a bit of a hack - but we set a models IsNew to true if
|
)
|
||||||
// we just filled it in for the first time, so when it comes
|
|
||||||
// time to insert them (post children callback) we can check for
|
|
||||||
// that bool being true - since it won't be true if it was already
|
|
||||||
// in the db
|
|
||||||
|
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
IDBase
|
IDBase
|
||||||
Name string `gorm:"not null; unique_index"`
|
Name string `gorm:"not null; unique_index"`
|
||||||
Folders []Folder
|
Albums []Album `gorm:"foreignkey:TagArtistID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
IDBase
|
IDBase
|
||||||
CrudBase
|
CrudBase
|
||||||
Folder Folder
|
Album Album
|
||||||
FolderID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES folders(id) ON DELETE CASCADE"`
|
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"`
|
Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"`
|
||||||
Artist Artist
|
Artist Artist
|
||||||
ArtistID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
ArtistID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
ContentType string `gorm:"not null" sql:"default: null"`
|
|
||||||
Duration int `gorm:"not null" sql:"default: null"`
|
Duration int `gorm:"not null" sql:"default: null"`
|
||||||
Size int `gorm:"not null" sql:"default: null"`
|
Size int `gorm:"not null" sql:"default: null"`
|
||||||
Bitrate int `gorm:"not null" sql:"default: null"`
|
Bitrate int `gorm:"not null" sql:"default: null"`
|
||||||
@@ -38,6 +33,19 @@ type Track struct {
|
|||||||
TagYear int `sql:"default: null"`
|
TagYear int `sql:"default: null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Track) Ext() string {
|
||||||
|
longExt := path.Ext(t.Filename)
|
||||||
|
if len(longExt) < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return longExt[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Track) MIME() string {
|
||||||
|
ext := t.Ext()
|
||||||
|
return mime.Types[ext]
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
IDBase
|
IDBase
|
||||||
CrudBase
|
CrudBase
|
||||||
@@ -55,25 +63,26 @@ type Setting struct {
|
|||||||
|
|
||||||
type Play struct {
|
type Play struct {
|
||||||
IDBase
|
IDBase
|
||||||
User User
|
User User
|
||||||
UserID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
|
UserID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
|
||||||
Folder Folder
|
Album Album
|
||||||
FolderID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES folders(id) ON DELETE CASCADE"`
|
AlbumID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
Time time.Time `sql:"default: null"`
|
Time time.Time `sql:"default: null"`
|
||||||
Count int
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Folder struct {
|
type Album struct {
|
||||||
IDBase
|
IDBase
|
||||||
CrudBase
|
CrudBase
|
||||||
Path string `gorm:"not null; unique_index" sql:"default: null"`
|
LeftPath string `gorm:"unique_index:idx_left_path_right_path"`
|
||||||
Parent *Folder
|
RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"`
|
||||||
ParentID int `sql:"default: null; type:int REFERENCES folders(id) ON DELETE CASCADE"`
|
Parent *Album
|
||||||
AlbumArtist Artist
|
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
AlbumArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
Cover string `sql:"default: null"`
|
||||||
AlbumTitle string `gorm:"index" sql:"default: null"`
|
TagArtist Artist
|
||||||
AlbumYear int `sql:"default: null"`
|
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
Cover string `sql:"default: null"`
|
TagTitle string `gorm:"index" sql:"default: null"`
|
||||||
Tracks []Track
|
TagYear int `sql:"default: null"`
|
||||||
IsNew bool `gorm:"-"`
|
Tracks []Track
|
||||||
|
IsNew bool `gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func New(db *gorm.DB, musicPath string) *Scanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) curFolder() *model.Folder {
|
func (s *Scanner) curFolder() *model.Album {
|
||||||
return s.curFolders.Peek()
|
return s.curFolders.Peek()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +45,26 @@ func (s *Scanner) curFolderID() int {
|
|||||||
return peek.ID
|
return peek.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) MigrateDB() error {
|
||||||
|
defer logElapsed(time.Now(), "migrating database")
|
||||||
|
s.tx = s.db.Begin()
|
||||||
|
defer s.tx.Commit()
|
||||||
|
s.tx.AutoMigrate(
|
||||||
|
model.Artist{},
|
||||||
|
model.Track{},
|
||||||
|
model.User{},
|
||||||
|
model.Setting{},
|
||||||
|
model.Play{},
|
||||||
|
model.Album{},
|
||||||
|
)
|
||||||
|
s.tx.FirstOrCreate(&model.User{}, model.User{
|
||||||
|
Name: "admin",
|
||||||
|
Password: "admin",
|
||||||
|
IsAdmin: true,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Scanner) Start() error {
|
func (s *Scanner) Start() error {
|
||||||
if atomic.LoadInt32(&IsScanning) == 1 {
|
if atomic.LoadInt32(&IsScanning) == 1 {
|
||||||
return errors.New("already scanning")
|
return errors.New("already scanning")
|
||||||
@@ -82,38 +102,22 @@ func (s *Scanner) startScan() error {
|
|||||||
|
|
||||||
func (s *Scanner) startClean() error {
|
func (s *Scanner) startClean() error {
|
||||||
defer logElapsed(time.Now(), "cleaning database")
|
defer logElapsed(time.Now(), "cleaning database")
|
||||||
var tracks []model.Track
|
var tracks []*model.Track
|
||||||
s.tx.
|
err := s.tx.
|
||||||
Select("id").
|
Select("id").
|
||||||
Find(&tracks)
|
Find(&tracks).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "scanning tracks")
|
||||||
|
}
|
||||||
var deleted int
|
var deleted int
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
_, ok := s.seenTracks[track.ID]
|
_, ok := s.seenTracks[track.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
s.tx.Delete(&track)
|
s.tx.Delete(track)
|
||||||
deleted++
|
deleted++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("removed %d tracks\n", deleted)
|
log.Printf("removed %d tracks\n", deleted)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) MigrateDB() error {
|
|
||||||
defer logElapsed(time.Now(), "migrating database")
|
|
||||||
s.tx = s.db.Begin()
|
|
||||||
defer s.tx.Commit()
|
|
||||||
s.tx.AutoMigrate(
|
|
||||||
model.Artist{},
|
|
||||||
model.Track{},
|
|
||||||
model.User{},
|
|
||||||
model.Setting{},
|
|
||||||
model.Play{},
|
|
||||||
model.Folder{},
|
|
||||||
)
|
|
||||||
s.tx.FirstOrCreate(&model.User{}, model.User{
|
|
||||||
Name: "admin",
|
|
||||||
Password: "admin",
|
|
||||||
IsAdmin: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
"github.com/sentriz/gonic/model"
|
"github.com/sentriz/gonic/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type folderStack []*model.Folder
|
type folderStack []*model.Album
|
||||||
|
|
||||||
func (s *folderStack) Push(v *model.Folder) {
|
func (s *folderStack) Push(v *model.Album) {
|
||||||
*s = append(*s, v)
|
*s = append(*s, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *folderStack) Pop() *model.Folder {
|
func (s *folderStack) Pop() *model.Album {
|
||||||
l := len(*s)
|
l := len(*s)
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -23,7 +23,7 @@ func (s *folderStack) Pop() *model.Folder {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *folderStack) Peek() *model.Folder {
|
func (s *folderStack) Peek() *model.Album {
|
||||||
l := len(*s)
|
l := len(*s)
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -34,7 +34,7 @@ func (s *folderStack) Peek() *model.Folder {
|
|||||||
func (s *folderStack) String() string {
|
func (s *folderStack) String() string {
|
||||||
paths := make([]string, len(*s))
|
paths := make([]string, len(*s))
|
||||||
for i, folder := range *s {
|
for i, folder := range *s {
|
||||||
paths[i] = folder.Path
|
paths[i] = folder.LeftPath
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s]", strings.Join(paths, " "))
|
return fmt.Sprintf("[%s]", strings.Join(paths, " "))
|
||||||
}
|
}
|
||||||
@@ -7,27 +7,19 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mimeTypes = map[string]string{
|
|
||||||
"mp3": "audio/mpeg",
|
|
||||||
"flac": "audio/x-flac",
|
|
||||||
"aac": "audio/x-aac",
|
|
||||||
"m4a": "audio/m4a",
|
|
||||||
"ogg": "audio/ogg",
|
|
||||||
}
|
|
||||||
|
|
||||||
var coverFilenames = map[string]struct{}{
|
var coverFilenames = map[string]struct{}{
|
||||||
"cover.png": struct{}{},
|
"cover.png": {},
|
||||||
"cover.jpg": struct{}{},
|
"cover.jpg": {},
|
||||||
"cover.jpeg": struct{}{},
|
"cover.jpeg": {},
|
||||||
"folder.png": struct{}{},
|
"folder.png": {},
|
||||||
"folder.jpg": struct{}{},
|
"folder.jpg": {},
|
||||||
"folder.jpeg": struct{}{},
|
"folder.jpeg": {},
|
||||||
"album.png": struct{}{},
|
"album.png": {},
|
||||||
"album.jpg": struct{}{},
|
"album.jpg": {},
|
||||||
"album.jpeg": struct{}{},
|
"album.jpeg": {},
|
||||||
"front.png": struct{}{},
|
"front.png": {},
|
||||||
"front.jpg": struct{}{},
|
"front.jpg": {},
|
||||||
"front.jpeg": struct{}{},
|
"front.jpeg": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTags(path string) (tag.Metadata, error) {
|
func readTags(path string) (tag.Metadata, error) {
|
||||||
|
|||||||
@@ -10,20 +10,16 @@ import (
|
|||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/sentriz/gonic/mime"
|
||||||
"github.com/sentriz/gonic/model"
|
"github.com/sentriz/gonic/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type item struct {
|
type item struct {
|
||||||
//
|
fullPath string
|
||||||
// common
|
relPath string
|
||||||
fullPath string
|
directory string
|
||||||
relPath string
|
filename string
|
||||||
filename string
|
stat os.FileInfo
|
||||||
stat os.FileInfo
|
|
||||||
//
|
|
||||||
// track only
|
|
||||||
ext string
|
|
||||||
mime string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
||||||
@@ -35,12 +31,13 @@ func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "getting relative path")
|
return errors.Wrap(err, "getting relative path")
|
||||||
}
|
}
|
||||||
_, filename := path.Split(relPath)
|
directory, filename := path.Split(relPath)
|
||||||
it := &item{
|
it := &item{
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
relPath: relPath,
|
relPath: relPath,
|
||||||
filename: filename,
|
directory: directory,
|
||||||
stat: stat,
|
filename: filename,
|
||||||
|
stat: stat,
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return s.handleFolder(it)
|
return s.handleFolder(it)
|
||||||
@@ -50,9 +47,7 @@ func (s *Scanner) callbackItem(fullPath string, info *godirwalk.Dirent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ext := path.Ext(filename)[1:]
|
ext := path.Ext(filename)[1:]
|
||||||
if mime, ok := mimeTypes[ext]; ok {
|
if _, ok := mime.Types[ext]; ok {
|
||||||
it.ext = ext
|
|
||||||
it.mime = mime
|
|
||||||
return s.handleTrack(it)
|
return s.handleTrack(it)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -71,9 +66,12 @@ func (s *Scanner) callbackPost(fullPath string, info *godirwalk.Dirent) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) handleFolder(it *item) error {
|
func (s *Scanner) handleFolder(it *item) error {
|
||||||
var folder model.Folder
|
var folder model.Album
|
||||||
err := s.tx.
|
err := s.tx.
|
||||||
Where("path = ?", it.relPath).
|
Where(model.Album{
|
||||||
|
LeftPath: it.directory,
|
||||||
|
RightPath: it.filename,
|
||||||
|
}).
|
||||||
First(&folder).
|
First(&folder).
|
||||||
Error
|
Error
|
||||||
if !gorm.IsRecordNotFoundError(err) &&
|
if !gorm.IsRecordNotFoundError(err) &&
|
||||||
@@ -82,7 +80,8 @@ func (s *Scanner) handleFolder(it *item) error {
|
|||||||
s.curFolders.Push(&folder)
|
s.curFolders.Push(&folder)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
folder.Path = it.relPath
|
folder.LeftPath = it.directory
|
||||||
|
folder.RightPath = it.filename
|
||||||
s.tx.Save(&folder)
|
s.tx.Save(&folder)
|
||||||
folder.IsNew = true
|
folder.IsNew = true
|
||||||
s.curFolders.Push(&folder)
|
s.curFolders.Push(&folder)
|
||||||
@@ -95,7 +94,7 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
var track model.Track
|
var track model.Track
|
||||||
err := s.tx.
|
err := s.tx.
|
||||||
Where(model.Track{
|
Where(model.Track{
|
||||||
FolderID: s.curFolderID(),
|
AlbumID: s.curFolderID(),
|
||||||
Filename: it.filename,
|
Filename: it.filename,
|
||||||
}).
|
}).
|
||||||
First(&track).
|
First(&track).
|
||||||
@@ -107,9 +106,8 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
track.Filename = it.filename
|
track.Filename = it.filename
|
||||||
track.ContentType = it.mime
|
|
||||||
track.Size = int(it.stat.Size())
|
track.Size = int(it.stat.Size())
|
||||||
track.FolderID = s.curFolderID()
|
track.AlbumID = s.curFolderID()
|
||||||
track.Duration = -1
|
track.Duration = -1
|
||||||
track.Bitrate = -1
|
track.Bitrate = -1
|
||||||
tags, err := readTags(it.fullPath)
|
tags, err := readTags(it.fullPath)
|
||||||
@@ -128,7 +126,8 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
//
|
//
|
||||||
// set album artist basics
|
// set album artist basics
|
||||||
var artist model.Artist
|
var artist model.Artist
|
||||||
err = s.tx.Where("name = ?", tags.AlbumArtist()).
|
err = s.tx.
|
||||||
|
Where("name = ?", tags.AlbumArtist()).
|
||||||
First(&artist).
|
First(&artist).
|
||||||
Error
|
Error
|
||||||
if gorm.IsRecordNotFoundError(err) {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
@@ -143,8 +142,8 @@ func (s *Scanner) handleTrack(it *item) error {
|
|||||||
if !s.curFolder().IsNew {
|
if !s.curFolder().IsNew {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.curFolder().AlbumTitle = tags.Album()
|
s.curFolder().TagTitle = tags.Album()
|
||||||
s.curFolder().AlbumYear = tags.Year()
|
s.curFolder().TagYear = tags.Year()
|
||||||
s.curFolder().AlbumArtistID = artist.ID
|
s.curFolder().TagArtistID = artist.ID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/sentriz/gonic/model"
|
"github.com/sentriz/gonic/model"
|
||||||
"github.com/sentriz/gonic/server/subsonic"
|
"github.com/sentriz/gonic/server/subsonic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Track {
|
func makeChildFromFolder(f *model.Album, parent *model.Album) *subsonic.Track {
|
||||||
child := &subsonic.Track{
|
child := &subsonic.Track{
|
||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
Title: f.Name,
|
CoverID: f.ID,
|
||||||
CoverID: f.CoverID,
|
Title: f.RightPath,
|
||||||
IsDir: true,
|
IsDir: true,
|
||||||
}
|
}
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
@@ -18,48 +20,52 @@ func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Track
|
|||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Track {
|
func makeChildFromTrack(t *model.Track, parent *model.Album) *subsonic.Track {
|
||||||
return &subsonic.Track{
|
return &subsonic.Track{
|
||||||
ID: t.ID,
|
ID: t.ID,
|
||||||
Album: t.Album.Title,
|
Album: t.Album.RightPath,
|
||||||
Artist: t.TrackArtist,
|
ContentType: t.MIME(),
|
||||||
ContentType: t.ContentType,
|
Suffix: t.Ext(),
|
||||||
Path: t.Path,
|
|
||||||
Size: t.Size,
|
Size: t.Size,
|
||||||
Suffix: t.Suffix,
|
Artist: t.TagTrackArtist,
|
||||||
Title: t.Title,
|
Title: t.TagTitle,
|
||||||
TrackNumber: t.TrackNumber,
|
TrackNumber: t.TagTrackNumber,
|
||||||
ParentID: parent.ID,
|
Path: path.Join(
|
||||||
CoverID: parent.CoverID,
|
parent.LeftPath,
|
||||||
Duration: 0,
|
parent.RightPath,
|
||||||
IsDir: false,
|
t.Filename,
|
||||||
Type: "music",
|
),
|
||||||
|
ParentID: parent.ID,
|
||||||
|
CoverID: parent.ID,
|
||||||
|
Duration: 0,
|
||||||
|
IsDir: false,
|
||||||
|
Type: "music",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAlbumFromFolder(f *model.Folder) *subsonic.Album {
|
func makeAlbumFromFolder(f *model.Album) *subsonic.Album {
|
||||||
return &subsonic.Album{
|
return &subsonic.Album{
|
||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
Title: f.Name,
|
Title: f.RightPath,
|
||||||
CoverID: f.CoverID,
|
CoverID: f.ID,
|
||||||
ParentID: f.ParentID,
|
ParentID: f.ParentID,
|
||||||
Artist: f.Parent.Name,
|
Artist: f.Parent.RightPath,
|
||||||
IsDir: true,
|
IsDir: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeArtistFromFolder(f *model.Folder) *subsonic.Artist {
|
func makeArtistFromFolder(f *model.Album) *subsonic.Artist {
|
||||||
return &subsonic.Artist{
|
return &subsonic.Artist{
|
||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
Name: f.Name,
|
Name: f.RightPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDirFromFolder(f *model.Folder, children []*subsonic.Track) *subsonic.Directory {
|
func makeDirFromFolder(f *model.Album, children []*subsonic.Track) *subsonic.Directory {
|
||||||
return &subsonic.Directory{
|
return &subsonic.Directory{
|
||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
Parent: f.ParentID,
|
Parent: f.ParentID,
|
||||||
Name: f.Name,
|
Name: f.RightPath,
|
||||||
Children: children,
|
Children: children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/sentriz/gonic/model"
|
"github.com/sentriz/gonic/model"
|
||||||
"github.com/sentriz/gonic/server/subsonic"
|
"github.com/sentriz/gonic/server/subsonic"
|
||||||
)
|
)
|
||||||
@@ -8,9 +10,9 @@ import (
|
|||||||
func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
|
func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
|
||||||
return &subsonic.Album{
|
return &subsonic.Album{
|
||||||
ID: a.ID,
|
ID: a.ID,
|
||||||
Name: a.Title,
|
Name: a.TagTitle,
|
||||||
Created: a.CreatedAt,
|
Created: a.CreatedAt,
|
||||||
CoverID: a.CoverID,
|
CoverID: a.ID,
|
||||||
Artist: artist.Name,
|
Artist: artist.Name,
|
||||||
ArtistID: artist.ID,
|
ArtistID: artist.ID,
|
||||||
}
|
}
|
||||||
@@ -19,20 +21,24 @@ func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
|
|||||||
func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track {
|
func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track {
|
||||||
return &subsonic.Track{
|
return &subsonic.Track{
|
||||||
ID: t.ID,
|
ID: t.ID,
|
||||||
Title: t.Title,
|
ContentType: t.MIME(),
|
||||||
Artist: t.TrackArtist,
|
Suffix: t.Ext(),
|
||||||
TrackNumber: t.TrackNumber,
|
ParentID: t.AlbumID,
|
||||||
ContentType: t.ContentType,
|
|
||||||
Path: t.Path,
|
|
||||||
ParentID: t.FolderID,
|
|
||||||
Suffix: t.Suffix,
|
|
||||||
CreatedAt: t.CreatedAt,
|
CreatedAt: t.CreatedAt,
|
||||||
Size: t.Size,
|
Size: t.Size,
|
||||||
Album: album.Title,
|
Title: t.TagTitle,
|
||||||
AlbumID: album.ID,
|
Artist: t.TagTrackArtist,
|
||||||
ArtistID: album.Artist.ID,
|
TrackNumber: t.TagTrackNumber,
|
||||||
CoverID: album.CoverID,
|
Path: path.Join(
|
||||||
Type: "music",
|
album.LeftPath,
|
||||||
|
album.RightPath,
|
||||||
|
t.Filename,
|
||||||
|
),
|
||||||
|
Album: album.TagTitle,
|
||||||
|
AlbumID: album.ID,
|
||||||
|
ArtistID: album.TagArtist.ID,
|
||||||
|
CoverID: album.ID,
|
||||||
|
Type: "music",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ type Controller struct {
|
|||||||
|
|
||||||
func (c *Controller) GetSetting(key string) string {
|
func (c *Controller) GetSetting(key string) string {
|
||||||
var setting model.Setting
|
var setting model.Setting
|
||||||
c.DB.Where("key = ?", key).First(&setting)
|
c.DB.
|
||||||
|
Where("key = ?", key).
|
||||||
|
First(&setting)
|
||||||
return setting.Value
|
return setting.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,10 @@ func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var user model.User
|
var user model.User
|
||||||
err := c.DB.Where("name = ?", username).First(&user).Error
|
err := c.DB.
|
||||||
|
Where("name = ?", username).
|
||||||
|
First(&user).
|
||||||
|
Error
|
||||||
if gorm.IsRecordNotFoundError(err) {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
http.Error(w, "couldn't find a user with that name", 400)
|
http.Error(w, "couldn't find a user with that name", 400)
|
||||||
return
|
return
|
||||||
@@ -142,7 +145,9 @@ func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Reques
|
|||||||
session := r.Context().Value(contextSessionKey).(*sessions.Session)
|
session := r.Context().Value(contextSessionKey).(*sessions.Session)
|
||||||
username := r.URL.Query().Get("user")
|
username := r.URL.Query().Get("user")
|
||||||
var user model.User
|
var user model.User
|
||||||
c.DB.Where("name = ?", username).First(&user)
|
c.DB.
|
||||||
|
Where("name = ?", username).
|
||||||
|
First(&user)
|
||||||
passwordOne := r.FormValue("password_one")
|
passwordOne := r.FormValue("password_one")
|
||||||
passwordTwo := r.FormValue("password_two")
|
passwordTwo := r.FormValue("password_two")
|
||||||
err := validatePasswords(passwordOne, passwordTwo)
|
err := validatePasswords(passwordOne, passwordTwo)
|
||||||
@@ -164,7 +169,10 @@ func (c *Controller) ServeDeleteUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var user model.User
|
var user model.User
|
||||||
err := c.DB.Where("name = ?", username).First(&user).Error
|
err := c.DB.
|
||||||
|
Where("name = ?", username).
|
||||||
|
First(&user).
|
||||||
|
Error
|
||||||
if gorm.IsRecordNotFoundError(err) {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
http.Error(w, "couldn't find a user with that name", 400)
|
http.Error(w, "couldn't find a user with that name", 400)
|
||||||
return
|
return
|
||||||
@@ -177,7 +185,9 @@ func (c *Controller) ServeDeleteUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (c *Controller) ServeDeleteUserDo(w http.ResponseWriter, r *http.Request) {
|
func (c *Controller) ServeDeleteUserDo(w http.ResponseWriter, r *http.Request) {
|
||||||
username := r.URL.Query().Get("user")
|
username := r.URL.Query().Get("user")
|
||||||
var user model.User
|
var user model.User
|
||||||
c.DB.Where("name = ?", username).First(&user)
|
c.DB.
|
||||||
|
Where("name = ?", username).
|
||||||
|
First(&user)
|
||||||
c.DB.Delete(&user)
|
c.DB.Delete(&user)
|
||||||
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
|
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ func TestFirstExisting(t *testing.T) {
|
|||||||
"default"},
|
"default"},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc // pin
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
actu := firstExisting(tc.or, tc.values...)
|
actu := firstExisting(tc.or, tc.values...)
|
||||||
if actu != tc.exp {
|
if actu != tc.exp {
|
||||||
t.Errorf("expected %q, got %q", tc.exp, actu)
|
t.Errorf("expected %q, got %q", tc.exp, actu)
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ import (
|
|||||||
// under the root directory
|
// under the root directory
|
||||||
|
|
||||||
func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
|
func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
|
||||||
var folders []model.Folder
|
var folders []model.Album
|
||||||
c.DB.Where("parent_id = 1").Find(&folders)
|
c.DB.
|
||||||
|
Where("parent_id = 1").
|
||||||
|
Find(&folders)
|
||||||
var indexMap = make(map[rune]*subsonic.Index)
|
var indexMap = make(map[rune]*subsonic.Index)
|
||||||
var indexes []*subsonic.Index
|
var indexes []*subsonic.Index
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
i := indexOf(folder.Name)
|
i := indexOf(folder.RightPath)
|
||||||
index, ok := indexMap[i]
|
index, ok := indexMap[i]
|
||||||
if !ok {
|
if !ok {
|
||||||
index = &subsonic.Index{
|
index = &subsonic.Index{
|
||||||
@@ -37,7 +39,7 @@ func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
|
|||||||
index.Artists = append(index.Artists,
|
index.Artists = append(index.Artists,
|
||||||
makeArtistFromFolder(&folder))
|
makeArtistFromFolder(&folder))
|
||||||
}
|
}
|
||||||
sort.Slice(indexes[:], func(i, j int) bool {
|
sort.Slice(indexes, func(i, j int) bool {
|
||||||
return indexes[i].Name < indexes[j].Name
|
return indexes[i].Name < indexes[j].Name
|
||||||
})
|
})
|
||||||
sub := subsonic.NewResponse()
|
sub := subsonic.NewResponse()
|
||||||
@@ -55,11 +57,11 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
childrenObj := []*subsonic.Track{}
|
childrenObj := []*subsonic.Track{}
|
||||||
var folder model.Folder
|
var folder model.Album
|
||||||
c.DB.First(&folder, id)
|
c.DB.First(&folder, id)
|
||||||
//
|
//
|
||||||
// start looking for child childFolders in the current dir
|
// start looking for child childFolders in the current dir
|
||||||
var childFolders []model.Folder
|
var childFolders []model.Album
|
||||||
c.DB.
|
c.DB.
|
||||||
Where("parent_id = ?", id).
|
Where("parent_id = ?", id).
|
||||||
Find(&childFolders)
|
Find(&childFolders)
|
||||||
@@ -71,18 +73,18 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
|
|||||||
// start looking for child childTracks in the current dir
|
// start looking for child childTracks in the current dir
|
||||||
var childTracks []model.Track
|
var childTracks []model.Track
|
||||||
c.DB.
|
c.DB.
|
||||||
Where("folder_id = ?", id).
|
Where("album_id = ?", id).
|
||||||
Preload("Album").
|
Preload("Album").
|
||||||
Order("title").
|
Order("filename").
|
||||||
Find(&childTracks)
|
Find(&childTracks)
|
||||||
for _, c := range childTracks {
|
for _, c := range childTracks {
|
||||||
|
toAppend := makeChildFromTrack(&c, &folder)
|
||||||
if getStrParam(r, "c") == "Jamstash" {
|
if getStrParam(r, "c") == "Jamstash" {
|
||||||
// jamstash thinks it can't play flacs
|
// jamstash thinks it can't play flacs
|
||||||
c.ContentType = "audio/mpeg"
|
toAppend.ContentType = "audio/mpeg"
|
||||||
c.Suffix = "mp3"
|
toAppend.Suffix = "mp3"
|
||||||
}
|
}
|
||||||
childrenObj = append(childrenObj,
|
childrenObj = append(childrenObj, toAppend)
|
||||||
makeChildFromTrack(&c, &folder))
|
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// respond section
|
// respond section
|
||||||
@@ -103,16 +105,16 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch listType {
|
switch listType {
|
||||||
case "alphabeticalByArtist":
|
case "alphabeticalByArtist":
|
||||||
q = q.Joins(`
|
q = q.Joins(`
|
||||||
JOIN folders AS parent_folders
|
JOIN albums AS parent_albums
|
||||||
ON folders.parent_id = parent_folders.id`)
|
ON albums.parent_id = parent_albums.id`)
|
||||||
q = q.Order("parent_folders.name")
|
q = q.Order("parent_albums.right_path")
|
||||||
case "alphabeticalByName":
|
case "alphabeticalByName":
|
||||||
q = q.Order("name")
|
q = q.Order("right_path")
|
||||||
case "frequent":
|
case "frequent":
|
||||||
user := r.Context().Value(contextUserKey).(*model.User)
|
user := r.Context().Value(contextUserKey).(*model.User)
|
||||||
q = q.Joins(`
|
q = q.Joins(`
|
||||||
JOIN plays
|
JOIN plays
|
||||||
ON folders.id = plays.folder_id AND plays.user_id = ?`,
|
ON albums.id = plays.album_id AND plays.user_id = ?`,
|
||||||
user.ID)
|
user.ID)
|
||||||
q = q.Order("plays.count DESC")
|
q = q.Order("plays.count DESC")
|
||||||
case "newest":
|
case "newest":
|
||||||
@@ -123,7 +125,7 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
|
|||||||
user := r.Context().Value(contextUserKey).(*model.User)
|
user := r.Context().Value(contextUserKey).(*model.User)
|
||||||
q = q.Joins(`
|
q = q.Joins(`
|
||||||
JOIN plays
|
JOIN plays
|
||||||
ON folders.id = plays.folder_id AND plays.user_id = ?`,
|
ON albums.id = plays.album_id AND plays.user_id = ?`,
|
||||||
user.ID)
|
user.ID)
|
||||||
q = q.Order("plays.time DESC")
|
q = q.Order("plays.time DESC")
|
||||||
default:
|
default:
|
||||||
@@ -131,9 +133,9 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
|
|||||||
"unknown value `%s` for parameter 'type'", listType)
|
"unknown value `%s` for parameter 'type'", listType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var folders []model.Folder
|
var folders []model.Album
|
||||||
q.
|
q.
|
||||||
Where("folders.has_tracks = 1").
|
Where("albums.tag_artist_id IS NOT NULL").
|
||||||
Offset(getIntParamOr(r, "offset", 0)).
|
Offset(getIntParamOr(r, "offset", 0)).
|
||||||
Limit(getIntParamOr(r, "size", 10)).
|
Limit(getIntParamOr(r, "size", 10)).
|
||||||
Preload("Parent").
|
Preload("Parent").
|
||||||
@@ -158,9 +160,9 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
|
|||||||
results := &subsonic.SearchResultTwo{}
|
results := &subsonic.SearchResultTwo{}
|
||||||
//
|
//
|
||||||
// search "artists"
|
// search "artists"
|
||||||
var artists []model.Folder
|
var artists []model.Album
|
||||||
c.DB.
|
c.DB.
|
||||||
Where("parent_id = 1 AND name LIKE ?", query).
|
Where("parent_id = 1 AND right_path LIKE ?", query).
|
||||||
Offset(getIntParamOr(r, "artistOffset", 0)).
|
Offset(getIntParamOr(r, "artistOffset", 0)).
|
||||||
Limit(getIntParamOr(r, "artistCount", 20)).
|
Limit(getIntParamOr(r, "artistCount", 20)).
|
||||||
Find(&artists)
|
Find(&artists)
|
||||||
@@ -170,10 +172,10 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
//
|
//
|
||||||
// search "albums"
|
// search "albums"
|
||||||
var albums []model.Folder
|
var albums []model.Album
|
||||||
c.DB.
|
c.DB.
|
||||||
Preload("Parent").
|
Preload("Parent").
|
||||||
Where("has_tracks = 1 AND name LIKE ?", query).
|
Where("tag_artist_id IS NOT NULL AND right_path LIKE ?", query).
|
||||||
Offset(getIntParamOr(r, "albumOffset", 0)).
|
Offset(getIntParamOr(r, "albumOffset", 0)).
|
||||||
Limit(getIntParamOr(r, "albumCount", 20)).
|
Limit(getIntParamOr(r, "albumCount", 20)).
|
||||||
Find(&albums)
|
Find(&albums)
|
||||||
@@ -185,14 +187,14 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
|
|||||||
// search tracks
|
// search tracks
|
||||||
var tracks []model.Track
|
var tracks []model.Track
|
||||||
c.DB.
|
c.DB.
|
||||||
Preload("Folder").
|
Preload("Album").
|
||||||
Where("title LIKE ?", query).
|
Where("filename LIKE ?", query).
|
||||||
Offset(getIntParamOr(r, "songOffset", 0)).
|
Offset(getIntParamOr(r, "songOffset", 0)).
|
||||||
Limit(getIntParamOr(r, "songCount", 20)).
|
Limit(getIntParamOr(r, "songCount", 20)).
|
||||||
Find(&tracks)
|
Find(&tracks)
|
||||||
for _, t := range tracks {
|
for _, t := range tracks {
|
||||||
results.Tracks = append(results.Tracks,
|
results.Tracks = append(results.Tracks,
|
||||||
makeChildFromTrack(&t, &t.Folder))
|
makeChildFromTrack(&t, &t.Album))
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
sub := subsonic.NewResponse()
|
sub := subsonic.NewResponse()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func TestGetIndexes(t *testing.T) {
|
func TestGetIndexes(t *testing.T) {
|
||||||
testQueryCases(t, testController.GetIndexes, []*queryCase{
|
testQueryCases(t, testController.GetIndexes, []*queryCase{
|
||||||
{url.Values{"id": []string{"2"}}, "id_two", false},
|
{url.Values{}, "no_args", false},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
|
|||||||
index.Artists = append(index.Artists,
|
index.Artists = append(index.Artists,
|
||||||
makeArtistFromArtist(&artist))
|
makeArtistFromArtist(&artist))
|
||||||
}
|
}
|
||||||
sort.Slice(indexes.List[:], func(i, j int) bool {
|
sort.Slice(indexes.List, func(i, j int) bool {
|
||||||
return indexes.List[i].Name < indexes.List[j].Name
|
return indexes.List[i].Name < indexes.List[j].Name
|
||||||
})
|
})
|
||||||
sub := subsonic.NewResponse()
|
sub := subsonic.NewResponse()
|
||||||
@@ -66,9 +66,9 @@ func (c *Controller) GetAlbum(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
var album model.Album
|
var album model.Album
|
||||||
err = c.DB.
|
err = c.DB.
|
||||||
Preload("Artist").
|
Preload("TagArtist").
|
||||||
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
|
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Order("tracks.track_number")
|
return db.Order("tracks.tag_track_number")
|
||||||
}).
|
}).
|
||||||
First(&album, id).
|
First(&album, id).
|
||||||
Error
|
Error
|
||||||
@@ -77,7 +77,7 @@ func (c *Controller) GetAlbum(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
sub := subsonic.NewResponse()
|
sub := subsonic.NewResponse()
|
||||||
sub.Album = makeAlbumFromAlbum(&album, &album.Artist)
|
sub.Album = makeAlbumFromAlbum(&album, &album.TagArtist)
|
||||||
for _, track := range album.Tracks {
|
for _, track := range album.Tracks {
|
||||||
sub.Album.Tracks = append(sub.Album.Tracks,
|
sub.Album.Tracks = append(sub.Album.Tracks,
|
||||||
makeTrackFromTrack(&track, &album))
|
makeTrackFromTrack(&track, &album))
|
||||||
@@ -98,16 +98,16 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "alphabeticalByArtist":
|
case "alphabeticalByArtist":
|
||||||
q = q.Joins(`
|
q = q.Joins(`
|
||||||
JOIN artists
|
JOIN artists
|
||||||
ON albums.artist_id = artists.id`)
|
ON albums.tag_artist_id = artists.id`)
|
||||||
q = q.Order("artists.name")
|
q = q.Order("artists.name")
|
||||||
case "alphabeticalByName":
|
case "alphabeticalByName":
|
||||||
q = q.Order("title")
|
q = q.Order("tag_title")
|
||||||
case "byYear":
|
case "byYear":
|
||||||
q = q.Where(
|
q = q.Where(
|
||||||
"year BETWEEN ? AND ?",
|
"tag_year BETWEEN ? AND ?",
|
||||||
getIntParamOr(r, "fromYear", 1800),
|
getIntParamOr(r, "fromYear", 1800),
|
||||||
getIntParamOr(r, "toYear", 2200))
|
getIntParamOr(r, "toYear", 2200))
|
||||||
q = q.Order("year")
|
q = q.Order("tag_year")
|
||||||
case "frequent":
|
case "frequent":
|
||||||
user := r.Context().Value(contextUserKey).(*model.User)
|
user := r.Context().Value(contextUserKey).(*model.User)
|
||||||
q = q.Joins(`
|
q = q.Joins(`
|
||||||
@@ -133,15 +133,16 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
var albums []model.Album
|
var albums []model.Album
|
||||||
q.
|
q.
|
||||||
|
Where("albums.tag_artist_id IS NOT NULL").
|
||||||
Offset(getIntParamOr(r, "offset", 0)).
|
Offset(getIntParamOr(r, "offset", 0)).
|
||||||
Limit(getIntParamOr(r, "size", 10)).
|
Limit(getIntParamOr(r, "size", 10)).
|
||||||
Preload("Artist").
|
Preload("TagArtist").
|
||||||
Find(&albums)
|
Find(&albums)
|
||||||
sub := subsonic.NewResponse()
|
sub := subsonic.NewResponse()
|
||||||
sub.AlbumsTwo = &subsonic.Albums{}
|
sub.AlbumsTwo = &subsonic.Albums{}
|
||||||
for _, album := range albums {
|
for _, album := range albums {
|
||||||
sub.AlbumsTwo.List = append(sub.AlbumsTwo.List,
|
sub.AlbumsTwo.List = append(sub.AlbumsTwo.List,
|
||||||
makeAlbumFromAlbum(&album, &album.Artist))
|
makeAlbumFromAlbum(&album, &album.TagArtist))
|
||||||
}
|
}
|
||||||
respond(w, r, sub)
|
respond(w, r, sub)
|
||||||
}
|
}
|
||||||
@@ -171,21 +172,21 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
|
|||||||
// search "albums"
|
// search "albums"
|
||||||
var albums []model.Album
|
var albums []model.Album
|
||||||
c.DB.
|
c.DB.
|
||||||
Preload("Artist").
|
Preload("TagArtist").
|
||||||
Where("title LIKE ?", query).
|
Where("tag_title LIKE ?", query).
|
||||||
Offset(getIntParamOr(r, "albumOffset", 0)).
|
Offset(getIntParamOr(r, "albumOffset", 0)).
|
||||||
Limit(getIntParamOr(r, "albumCount", 20)).
|
Limit(getIntParamOr(r, "albumCount", 20)).
|
||||||
Find(&albums)
|
Find(&albums)
|
||||||
for _, a := range albums {
|
for _, a := range albums {
|
||||||
results.Albums = append(results.Albums,
|
results.Albums = append(results.Albums,
|
||||||
makeAlbumFromAlbum(&a, &a.Artist))
|
makeAlbumFromAlbum(&a, &a.TagArtist))
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// search tracks
|
// search tracks
|
||||||
var tracks []model.Track
|
var tracks []model.Track
|
||||||
c.DB.
|
c.DB.
|
||||||
Preload("Album").
|
Preload("Album").
|
||||||
Where("title LIKE ?", query).
|
Where("tag_title LIKE ?", query).
|
||||||
Offset(getIntParamOr(r, "songOffset", 0)).
|
Offset(getIntParamOr(r, "songOffset", 0)).
|
||||||
Limit(getIntParamOr(r, "songCount", 20)).
|
Limit(getIntParamOr(r, "songCount", 20)).
|
||||||
Find(&tracks)
|
Find(&tracks)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/rainycape/unidecode"
|
"github.com/rainycape/unidecode"
|
||||||
|
|
||||||
"github.com/sentriz/gonic/model"
|
"github.com/sentriz/gonic/model"
|
||||||
@@ -32,15 +33,20 @@ func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var track model.Track
|
var track model.Track
|
||||||
c.DB.
|
err = c.DB.
|
||||||
Preload("Album").
|
Preload("Album").
|
||||||
Preload("Folder").
|
First(&track, id).
|
||||||
First(&track, id)
|
Error
|
||||||
if track.Path == "" {
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
respondError(w, r, 70, "media with id `%d` was not found", id)
|
respondError(w, r, 70, "media with id `%d` was not found", id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
absPath := path.Join(c.MusicPath, track.Path)
|
absPath := path.Join(
|
||||||
|
c.MusicPath,
|
||||||
|
track.Album.LeftPath,
|
||||||
|
track.Album.RightPath,
|
||||||
|
track.Filename,
|
||||||
|
)
|
||||||
file, err := os.Open(absPath)
|
file, err := os.Open(absPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, r, 0, "error while streaming media: %v", err)
|
respondError(w, r, 0, "error while streaming media: %v", err)
|
||||||
@@ -52,11 +58,12 @@ func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
// after we've served the file, mark the album as played
|
// after we've served the file, mark the album as played
|
||||||
user := r.Context().Value(contextUserKey).(*model.User)
|
user := r.Context().Value(contextUserKey).(*model.User)
|
||||||
play := model.Play{
|
play := model.Play{
|
||||||
AlbumID: track.Album.ID,
|
AlbumID: track.Album.ID,
|
||||||
FolderID: track.Folder.ID,
|
UserID: user.ID,
|
||||||
UserID: user.ID,
|
|
||||||
}
|
}
|
||||||
c.DB.Where(play).First(&play)
|
c.DB.
|
||||||
|
Where(play).
|
||||||
|
First(&play)
|
||||||
play.Time = time.Now() // for getAlbumList?type=recent
|
play.Time = time.Now() // for getAlbumList?type=recent
|
||||||
play.Count++ // for getAlbumList?type=frequent
|
play.Count++ // for getAlbumList?type=frequent
|
||||||
c.DB.Save(&play)
|
c.DB.Save(&play)
|
||||||
@@ -68,9 +75,26 @@ func (c *Controller) GetCoverArt(w http.ResponseWriter, r *http.Request) {
|
|||||||
respondError(w, r, 10, "please provide an `id` parameter")
|
respondError(w, r, 10, "please provide an `id` parameter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var cover model.Cover
|
var folder model.Album
|
||||||
c.DB.First(&cover, id)
|
err = c.DB.
|
||||||
w.Write(cover.Image)
|
Select("id, path, cover").
|
||||||
|
First(&folder, id).
|
||||||
|
Error
|
||||||
|
if gorm.IsRecordNotFoundError(err) {
|
||||||
|
respondError(w, r, 10, "could not find a cover with that id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if folder.Cover == "" {
|
||||||
|
respondError(w, r, 10, "no cover found for that folder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
absPath := path.Join(
|
||||||
|
c.MusicPath,
|
||||||
|
folder.RightPath,
|
||||||
|
folder.LeftPath,
|
||||||
|
folder.Cover,
|
||||||
|
)
|
||||||
|
http.ServeFile(w, r, absPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetLicence(w http.ResponseWriter, r *http.Request) {
|
func (c *Controller) GetLicence(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Binary file not shown.
@@ -6,7 +6,7 @@
|
|||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1967) Easter Nowhere",
|
"title": "(1967) Easter Nowhere",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1994) The Graveyard and the Ballroom",
|
"title": "(1994) The Graveyard and the Ballroom",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1981) To Each.",
|
"title": "(1981) To Each.",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artist": "There",
|
"artist": "There",
|
||||||
"title": "(2010) Anika",
|
"title": "(2010) Anika",
|
||||||
"parent": 10,
|
"parent": 10,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1967) Easter Nowhere",
|
"title": "(1967) Easter Nowhere",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1981) To Each.",
|
"title": "(1981) To Each.",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1994) The Graveyard and the Ballroom",
|
"title": "(1994) The Graveyard and the Ballroom",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artist": "There",
|
"artist": "There",
|
||||||
"title": "(2010) Anika",
|
"title": "(2010) Anika",
|
||||||
"parent": 10,
|
"parent": 10,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artist": "There",
|
"artist": "There",
|
||||||
"title": "(2010) Anika",
|
"title": "(2010) Anika",
|
||||||
"parent": 10,
|
"parent": 10,
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1967) Easter Nowhere",
|
"title": "(1967) Easter Nowhere",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1981) To Each.",
|
"title": "(1981) To Each.",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1994) The Graveyard and the Ballroom",
|
"title": "(1994) The Graveyard and the Ballroom",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
|
|||||||
@@ -4,44 +4,45 @@
|
|||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"albumList": {
|
"albumList": {
|
||||||
"album": [
|
"album": [
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"coverArt": 4,
|
|
||||||
"artist": "There",
|
|
||||||
"title": "(2010) Anika",
|
|
||||||
"parent": 10,
|
|
||||||
"isDir": true,
|
|
||||||
"created": "0001-01-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"coverArt": 2,
|
|
||||||
"artist": "13th Floor Lowervators",
|
|
||||||
"title": "(1967) Easter Nowhere",
|
|
||||||
"parent": 5,
|
|
||||||
"isDir": true,
|
|
||||||
"created": "0001-01-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"title": "(1981) To Each.",
|
|
||||||
"parent": 2,
|
|
||||||
"isDir": true,
|
|
||||||
"created": "0001-01-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"title": "(1994) The Graveyard and the Ballroom",
|
"title": "(1994) The Graveyard and the Ballroom",
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
"isDir": true,
|
"isDir": true,
|
||||||
"created": "0001-01-01T00:00:00Z"
|
"created": "0001-01-01T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"coverArt": 11,
|
||||||
|
"artist": "There",
|
||||||
|
"title": "(2010) Anika",
|
||||||
|
"parent": 10,
|
||||||
|
"isDir": true,
|
||||||
|
"created": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"title": "(1981) To Each.",
|
||||||
|
"parent": 2,
|
||||||
|
"isDir": true,
|
||||||
|
"created": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"coverArt": 6,
|
||||||
|
"artist": "13th Floor Lowervators",
|
||||||
|
"title": "(1967) Easter Nowhere",
|
||||||
|
"parent": 5,
|
||||||
|
"isDir": true,
|
||||||
|
"created": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artist": "13th Floor Lowervators",
|
"artist": "13th Floor Lowervators",
|
||||||
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
|
|||||||
@@ -5,43 +5,44 @@
|
|||||||
"albumList2": {
|
"albumList2": {
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "Easter Everywhere",
|
"name": "Easter Everywhere",
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00"
|
"created": "2019-06-05T16:00:10.556862345+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "The Graveyard and the Ballroom",
|
"name": "The Graveyard and the Ballroom",
|
||||||
"created": "2019-05-28T20:59:02.988372626+01:00"
|
"created": "2019-06-05T16:00:10.54747823+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "To Each...",
|
"name": "To Each...",
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00"
|
"created": "2019-06-05T16:00:10.553065063+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artistId": 3,
|
"artistId": 3,
|
||||||
"artist": "Anikas",
|
"artist": "Anikas",
|
||||||
"name": "Anika",
|
"name": "Anika",
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,43 +5,44 @@
|
|||||||
"albumList2": {
|
"albumList2": {
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artistId": 3,
|
"artistId": 3,
|
||||||
"artist": "Anikas",
|
"artist": "Anikas",
|
||||||
"name": "Anika",
|
"name": "Anika",
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "Easter Everywhere",
|
"name": "Easter Everywhere",
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00"
|
"created": "2019-06-05T16:00:10.556862345+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "The Graveyard and the Ballroom",
|
"name": "The Graveyard and the Ballroom",
|
||||||
"created": "2019-05-28T20:59:02.988372626+01:00"
|
"created": "2019-06-05T16:00:10.54747823+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "To Each...",
|
"name": "To Each...",
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00"
|
"created": "2019-06-05T16:00:10.553065063+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,43 +5,44 @@
|
|||||||
"albumList2": {
|
"albumList2": {
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artistId": 3,
|
"artistId": 3,
|
||||||
"artist": "Anikas",
|
"artist": "Anikas",
|
||||||
"name": "Anika",
|
"name": "Anika",
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "Easter Everywhere",
|
"name": "Easter Everywhere",
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00"
|
"created": "2019-06-05T16:00:10.556862345+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "To Each...",
|
"name": "To Each...",
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00"
|
"created": "2019-06-05T16:00:10.553065063+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "The Graveyard and the Ballroom",
|
"name": "The Graveyard and the Ballroom",
|
||||||
"created": "2019-05-28T20:59:02.988372626+01:00"
|
"created": "2019-06-05T16:00:10.54747823+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,42 +6,43 @@
|
|||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"coverArt": 3,
|
|
||||||
"artistId": 2,
|
|
||||||
"artist": "13th Floor Elevators",
|
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"coverArt": 4,
|
"coverArt": 4,
|
||||||
"artistId": 3,
|
|
||||||
"artist": "Anikas",
|
|
||||||
"name": "Anika",
|
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"coverArt": 1,
|
|
||||||
"artistId": 1,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"name": "The Graveyard and the Ballroom",
|
|
||||||
"created": "2019-05-28T20:59:02.988372626+01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"coverArt": 2,
|
|
||||||
"artistId": 2,
|
|
||||||
"artist": "13th Floor Elevators",
|
|
||||||
"name": "Easter Everywhere",
|
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "To Each...",
|
"name": "To Each...",
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00"
|
"created": "2019-06-05T16:00:10.553065063+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"coverArt": 11,
|
||||||
|
"artistId": 3,
|
||||||
|
"artist": "Anikas",
|
||||||
|
"name": "Anika",
|
||||||
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"coverArt": 3,
|
||||||
|
"artistId": 1,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"name": "The Graveyard and the Ballroom",
|
||||||
|
"created": "2019-06-05T16:00:10.54747823+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"coverArt": 7,
|
||||||
|
"artistId": 2,
|
||||||
|
"artist": "13th Floor Elevators",
|
||||||
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"coverArt": 6,
|
||||||
|
"artistId": 2,
|
||||||
|
"artist": "13th Floor Elevators",
|
||||||
|
"name": "Easter Everywhere",
|
||||||
|
"created": "2019-06-05T16:00:10.556862345+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,181 +4,249 @@
|
|||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"album": {
|
"album": {
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"name": "Easter Everywhere",
|
"name": "The Graveyard and the Ballroom",
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00",
|
"created": "2019-06-05T16:00:10.54747823+01:00",
|
||||||
"song": [
|
"song": [
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.01067974+01:00",
|
"created": "2019-06-05T16:00:10.552108331+01:00",
|
||||||
"id": 25,
|
"id": 12,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/01.10 Slip Inside This House.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/01.14 Do the Du (casse).flac",
|
||||||
"size": 52229000,
|
"size": 20545509,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Slip Inside This House",
|
"title": "Do the Du (casse)",
|
||||||
"track": 1,
|
"track": 1,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.010814449+01:00",
|
"created": "2019-06-05T16:00:10.550632503+01:00",
|
||||||
"id": 27,
|
"id": 8,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/02.10 Slide Machine.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/02.14 Faceless.flac",
|
||||||
"size": 22964562,
|
"size": 16657561,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Slide Machine",
|
"title": "Faceless",
|
||||||
"track": 2,
|
"track": 2,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.010880444+01:00",
|
"created": "2019-06-05T16:00:10.549864008+01:00",
|
||||||
"id": 28,
|
"id": 6,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/03.10 She Lives (In a Time of Her Own).flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/03.14 Crippled Child.flac",
|
||||||
"size": 18474888,
|
"size": 21325811,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "She Lives (In a Time of Her Own)",
|
"title": "Crippled Child",
|
||||||
"track": 3,
|
"track": 3,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.011044654+01:00",
|
"created": "2019-06-05T16:00:10.551742258+01:00",
|
||||||
"id": 30,
|
"id": 11,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/04.10 Nobody to Love.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/04.14 Choir.flac",
|
||||||
"size": 18067448,
|
"size": 24728976,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Nobody to Love",
|
"title": "Choir",
|
||||||
"track": 4,
|
"track": 4,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.011106774+01:00",
|
"created": "2019-06-05T16:00:10.548393601+01:00",
|
||||||
"id": 31,
|
"id": 2,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/05.10 Baby Blue.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/05.14 Flight.flac",
|
||||||
"size": 31828836,
|
"size": 24860635,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Baby Blue",
|
"title": "Flight",
|
||||||
"track": 5,
|
"track": 5,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.011174899+01:00",
|
"created": "2019-06-05T16:00:10.552469878+01:00",
|
||||||
"id": 32,
|
"id": 13,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/06.10 Earthquake.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/06.14 I Feel.flac",
|
||||||
"size": 29066645,
|
"size": 16118749,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Earthquake",
|
"title": "I Feel",
|
||||||
"track": 6,
|
"track": 6,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.010597689+01:00",
|
"created": "2019-06-05T16:00:10.552842449+01:00",
|
||||||
"id": 24,
|
"id": 14,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/07.10 Dust.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/07.14 Strain.flac",
|
||||||
"size": 22652796,
|
"size": 17608752,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Dust",
|
"title": "Strain",
|
||||||
"track": 7,
|
"track": 7,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.010749083+01:00",
|
"created": "2019-06-05T16:00:10.549504916+01:00",
|
||||||
"id": 26,
|
"id": 5,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/08.10 Levitation.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/08.14 All Night Party.flac",
|
||||||
"size": 16354677,
|
"size": 24960016,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Levitation",
|
"title": "All Night Party",
|
||||||
"track": 8,
|
"track": 8,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.011238025+01:00",
|
"created": "2019-06-05T16:00:10.549134887+01:00",
|
||||||
"id": 33,
|
"id": 4,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/09.10 I Had to Tell You.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/09.14 Oceans.flac",
|
||||||
"size": 14261007,
|
"size": 26401567,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "I Had to Tell You",
|
"title": "Oceans",
|
||||||
"track": 9,
|
"track": 9,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "Easter Everywhere",
|
"album": "The Graveyard and the Ballroom",
|
||||||
"albumId": 3,
|
"albumId": 3,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "A Certain Ratio",
|
||||||
"artistId": 2,
|
"artistId": 1,
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 2,
|
"coverArt": 3,
|
||||||
"created": "2019-05-28T20:59:03.010949903+01:00",
|
"created": "2019-06-05T16:00:10.551373999+01:00",
|
||||||
"id": 29,
|
"id": 10,
|
||||||
"parent": 6,
|
"parent": 3,
|
||||||
"path": "13th Floor Lowervators/(1967) Easter Nowhere/10.10 Pictures (Leave Your Body Behind).flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/10.14 The Choir.flac",
|
||||||
"size": 39529576,
|
"size": 24106680,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Pictures (Leave Your Body Behind)",
|
"title": "The Choir",
|
||||||
"track": 10,
|
"track": 10,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "The Graveyard and the Ballroom",
|
||||||
|
"albumId": 3,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"artistId": 1,
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "2019-06-05T16:00:10.550996621+01:00",
|
||||||
|
"id": 9,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/11.14 The Fox.flac",
|
||||||
|
"size": 24054498,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "The Fox",
|
||||||
|
"track": 11,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "The Graveyard and the Ballroom",
|
||||||
|
"albumId": 3,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"artistId": 1,
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "2019-06-05T16:00:10.55027053+01:00",
|
||||||
|
"id": 7,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/12.14 Suspect.flac",
|
||||||
|
"size": 16592296,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Suspect",
|
||||||
|
"track": 12,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "The Graveyard and the Ballroom",
|
||||||
|
"albumId": 3,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"artistId": 1,
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "2019-06-05T16:00:10.547986038+01:00",
|
||||||
|
"id": 1,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/13.14 Flight.flac",
|
||||||
|
"size": 37302417,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Flight",
|
||||||
|
"track": 13,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "The Graveyard and the Ballroom",
|
||||||
|
"albumId": 3,
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"artistId": 1,
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "2019-06-05T16:00:10.548763765+01:00",
|
||||||
|
"id": 3,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/14.14 Genotype_Phenotype.flac",
|
||||||
|
"size": 24349252,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Genotype/Phenotype",
|
||||||
|
"track": 14,
|
||||||
|
"type": "music"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,156 +4,8 @@
|
|||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"album": {
|
"album": {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"artistId": 1,
|
"coverArt": 2,
|
||||||
"artist": "A Certain Ratio",
|
"created": "2019-06-05T16:00:10.547172057+01:00"
|
||||||
"name": "To Each...",
|
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00",
|
|
||||||
"song": [
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.99562727+01:00",
|
|
||||||
"id": 16,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./01.09 Felch.flac",
|
|
||||||
"size": 24708838,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Felch",
|
|
||||||
"track": 1,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.995889638+01:00",
|
|
||||||
"id": 20,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./02.09 My Spirit.flac",
|
|
||||||
"size": 17102404,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "My Spirit",
|
|
||||||
"track": 2,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.99609285+01:00",
|
|
||||||
"id": 23,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./03.09 Forced Laugh.flac",
|
|
||||||
"size": 37924980,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Forced Laugh",
|
|
||||||
"track": 3,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.995953401+01:00",
|
|
||||||
"id": 21,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./04.09 Choir.flac",
|
|
||||||
"size": 21205583,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Choir",
|
|
||||||
"track": 4,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.99569934+01:00",
|
|
||||||
"id": 17,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./05.09 Back to the Start.flac",
|
|
||||||
"size": 56733069,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Back to the Start",
|
|
||||||
"track": 5,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.995826449+01:00",
|
|
||||||
"id": 19,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./06.09 The Fox.flac",
|
|
||||||
"size": 26835335,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "The Fox",
|
|
||||||
"track": 6,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.995544892+01:00",
|
|
||||||
"id": 15,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./07.09 Loss.flac",
|
|
||||||
"size": 20494369,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Loss",
|
|
||||||
"track": 7,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.995761233+01:00",
|
|
||||||
"id": 18,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./08.09 Oceans.flac",
|
|
||||||
"size": 25233096,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Oceans",
|
|
||||||
"track": 8,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "To Each...",
|
|
||||||
"albumId": 2,
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"artistId": 1,
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"created": "2019-05-28T20:59:02.99603217+01:00",
|
|
||||||
"id": 22,
|
|
||||||
"parent": 4,
|
|
||||||
"path": "A Certain Ratio/(1981) To Each./09.09 Winter Hill.flac",
|
|
||||||
"size": 89483446,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Winter Hill",
|
|
||||||
"track": 9,
|
|
||||||
"type": "music"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,20 @@
|
|||||||
"name": "A Certain Ratio",
|
"name": "A Certain Ratio",
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 3,
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "The Graveyard and the Ballroom",
|
"name": "The Graveyard and the Ballroom",
|
||||||
"created": "2019-05-28T20:59:02.988372626+01:00"
|
"created": "2019-06-05T16:00:10.54747823+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 4,
|
||||||
|
"coverArt": 4,
|
||||||
"artistId": 1,
|
"artistId": 1,
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"name": "To Each...",
|
"name": "To Each...",
|
||||||
"created": "2019-05-28T20:59:02.995320471+01:00"
|
"created": "2019-06-05T16:00:10.553065063+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
"name": "Anikas",
|
"name": "Anikas",
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artistId": 3,
|
"artistId": 3,
|
||||||
"artist": "Anikas",
|
"artist": "Anikas",
|
||||||
"name": "Anika",
|
"name": "Anika",
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,20 @@
|
|||||||
"name": "13th Floor Elevators",
|
"name": "13th Floor Elevators",
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 6,
|
||||||
"coverArt": 2,
|
"coverArt": 6,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "Easter Everywhere",
|
"name": "Easter Everywhere",
|
||||||
"created": "2019-05-28T20:59:03.010415595+01:00"
|
"created": "2019-06-05T16:00:10.556862345+01:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "13th Floor Lowervators"
|
"name": "13th Floor Lowervators"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "___Anika"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -20,10 +24,6 @@
|
|||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "A Certain Ratio"
|
"name": "A Certain Ratio"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"name": "Anika"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -8,55 +8,10 @@
|
|||||||
"name": "(1994) The Graveyard and the Ballroom",
|
"name": "(1994) The Graveyard and the Ballroom",
|
||||||
"child": [
|
"child": [
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
|
||||||
"id": 5,
|
|
||||||
"parent": 3,
|
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/08.14 All Night Party.flac",
|
|
||||||
"size": 24960016,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "All Night Party",
|
|
||||||
"track": 8,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "The Graveyard and the Ballroom",
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"coverArt": 1,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
|
||||||
"id": 11,
|
|
||||||
"parent": 3,
|
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/04.14 Choir.flac",
|
|
||||||
"size": 24728976,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Choir",
|
|
||||||
"track": 4,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "The Graveyard and the Ballroom",
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"coverArt": 1,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
|
||||||
"id": 6,
|
|
||||||
"parent": 3,
|
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/03.14 Crippled Child.flac",
|
|
||||||
"size": 21325811,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Crippled Child",
|
|
||||||
"track": 3,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "The Graveyard and the Ballroom",
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"coverArt": 1,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -68,10 +23,10 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -83,25 +38,40 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 1,
|
"id": 6,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/13.14 Flight.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/03.14 Crippled Child.flac",
|
||||||
"size": 37302417,
|
"size": 21325811,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Flight",
|
"title": "Crippled Child",
|
||||||
"track": 13,
|
"track": 3,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 11,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/04.14 Choir.flac",
|
||||||
|
"size": 24728976,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Choir",
|
||||||
|
"track": 4,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -113,25 +83,10 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
|
||||||
"id": 3,
|
|
||||||
"parent": 3,
|
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/14.14 Genotype_Phenotype.flac",
|
|
||||||
"size": 24349252,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Genotype/Phenotype",
|
|
||||||
"track": 14,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "The Graveyard and the Ballroom",
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"coverArt": 1,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 13,
|
"id": 13,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -143,25 +98,10 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
|
||||||
"id": 4,
|
|
||||||
"parent": 3,
|
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/09.14 Oceans.flac",
|
|
||||||
"size": 26401567,
|
|
||||||
"suffix": "flac",
|
|
||||||
"title": "Oceans",
|
|
||||||
"track": 9,
|
|
||||||
"type": "music"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"album": "The Graveyard and the Ballroom",
|
|
||||||
"artist": "A Certain Ratio",
|
|
||||||
"contentType": "audio/x-flac",
|
|
||||||
"coverArt": 1,
|
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 14,
|
"id": 14,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -173,25 +113,40 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 7,
|
"id": 5,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/12.14 Suspect.flac",
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/08.14 All Night Party.flac",
|
||||||
"size": 16592296,
|
"size": 24960016,
|
||||||
"suffix": "flac",
|
"suffix": "flac",
|
||||||
"title": "Suspect",
|
"title": "All Night Party",
|
||||||
"track": 12,
|
"track": 8,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 4,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/09.14 Oceans.flac",
|
||||||
|
"size": 26401567,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Oceans",
|
||||||
|
"track": 9,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -203,10 +158,10 @@
|
|||||||
"type": "music"
|
"type": "music"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"album": "The Graveyard and the Ballroom",
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
"artist": "A Certain Ratio",
|
"artist": "A Certain Ratio",
|
||||||
"contentType": "audio/x-flac",
|
"contentType": "audio/x-flac",
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"parent": 3,
|
"parent": 3,
|
||||||
@@ -216,6 +171,51 @@
|
|||||||
"title": "The Fox",
|
"title": "The Fox",
|
||||||
"track": 11,
|
"track": 11,
|
||||||
"type": "music"
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 7,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/12.14 Suspect.flac",
|
||||||
|
"size": 16592296,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Suspect",
|
||||||
|
"track": 12,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 1,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/13.14 Flight.flac",
|
||||||
|
"size": 37302417,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Flight",
|
||||||
|
"track": 13,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 3,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/14.14 Genotype_Phenotype.flac",
|
||||||
|
"size": 24349252,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Genotype/Phenotype",
|
||||||
|
"track": 14,
|
||||||
|
"type": "music"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"name": "A Certain Ratio",
|
"name": "A Certain Ratio",
|
||||||
"child": [
|
"child": [
|
||||||
{
|
{
|
||||||
"coverArt": 1,
|
"coverArt": 3,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"isDir": true,
|
"isDir": true,
|
||||||
@@ -16,9 +16,10 @@
|
|||||||
"title": "(1994) The Graveyard and the Ballroom"
|
"title": "(1994) The Graveyard and the Ballroom"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"coverArt": 4,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"isDir": true,
|
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
"isDir": true,
|
||||||
"parent": 2,
|
"parent": 2,
|
||||||
"title": "(1981) To Each."
|
"title": "(1981) To Each."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
],
|
],
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 7,
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"artistId": 2,
|
"artistId": 2,
|
||||||
"artist": "13th Floor Elevators",
|
"artist": "13th Floor Elevators",
|
||||||
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
"name": "The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
"created": "2019-05-28T20:59:03.022922683+01:00"
|
"created": "2019-06-05T16:00:10.560355528+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
],
|
],
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 11,
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"artistId": 3,
|
"artistId": 3,
|
||||||
"artist": "Anikas",
|
"artist": "Anikas",
|
||||||
"name": "Anika",
|
"name": "Anika",
|
||||||
"created": "2019-05-28T20:59:03.035442597+01:00"
|
"created": "2019-06-05T16:00:10.565661506+01:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,45 @@
|
|||||||
],
|
],
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"coverArt": 3,
|
"coverArt": 7,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"isDir": true,
|
"isDir": true,
|
||||||
"parent": 5,
|
"parent": 5,
|
||||||
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators"
|
"title": "(1966) The Psychedelic Sounds of the 13th Floor Elevators"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"song": [
|
||||||
|
{
|
||||||
|
"album": "(1994) The Graveyard and the Ballroom",
|
||||||
|
"artist": "A Certain Ratio",
|
||||||
|
"contentType": "audio/x-flac",
|
||||||
|
"coverArt": 3,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 1,
|
||||||
|
"parent": 3,
|
||||||
|
"path": "A Certain Ratio/(1994) The Graveyard and the Ballroom/13.14 Flight.flac",
|
||||||
|
"size": 37302417,
|
||||||
|
"suffix": "flac",
|
||||||
|
"title": "Flight",
|
||||||
|
"track": 13,
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"album": "(1966) The Psychedelic Sounds of the 13th Floor Elevators",
|
||||||
|
"artist": "13th Floor Elevators",
|
||||||
|
"contentType": "audio/mpeg",
|
||||||
|
"coverArt": 7,
|
||||||
|
"created": "0001-01-01T00:00:00Z",
|
||||||
|
"id": 35,
|
||||||
|
"parent": 7,
|
||||||
|
"path": "13th Floor Lowervators/(1966) The Psychedelic Sounds of the 13th Floor Elevators/13.21 Before You Accuse Me.mp3",
|
||||||
|
"size": 4722688,
|
||||||
|
"suffix": "mp3",
|
||||||
|
"title": "Before You Accuse Me",
|
||||||
|
"track": 13,
|
||||||
|
"type": "music"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
{
|
{
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"parent": 1,
|
"parent": 1,
|
||||||
"name": "Anika"
|
"name": "___Anika"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"album": [
|
"album": [
|
||||||
{
|
{
|
||||||
"coverArt": 4,
|
"coverArt": 11,
|
||||||
"created": "0001-01-01T00:00:00Z",
|
"created": "0001-01-01T00:00:00Z",
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"isDir": true,
|
"isDir": true,
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ func Scrobble(apiKey, secret, session string, track *model.Track,
|
|||||||
}
|
}
|
||||||
params.Add("api_key", apiKey)
|
params.Add("api_key", apiKey)
|
||||||
params.Add("sk", session)
|
params.Add("sk", session)
|
||||||
params.Add("artist", track.TrackArtist)
|
params.Add("artist", track.TagTrackArtist)
|
||||||
params.Add("track", track.Title)
|
params.Add("track", track.TagTitle)
|
||||||
params.Add("album", track.Album.Title)
|
params.Add("album", track.Album.TagTitle)
|
||||||
params.Add("albumArtist", track.Artist.Name)
|
params.Add("albumArtist", track.Artist.Name)
|
||||||
params.Add("trackNumber", strconv.Itoa(track.TrackNumber))
|
params.Add("trackNumber", strconv.Itoa(track.TagTrackNumber))
|
||||||
params.Add("api_sig", getParamSignature(params, secret))
|
params.Add("api_sig", getParamSignature(params, secret))
|
||||||
_, err := makeRequest("POST", params)
|
_, err := makeRequest("POST", params)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
|
||||||
|
|
||||||
"github.com/sentriz/gonic/server/handler"
|
"github.com/sentriz/gonic/server/handler"
|
||||||
)
|
)
|
||||||
@@ -43,12 +42,9 @@ func New(db *gorm.DB, musicPath string, listenAddr string) *Server {
|
|||||||
DB: db,
|
DB: db,
|
||||||
MusicPath: musicPath,
|
MusicPath: musicPath,
|
||||||
}
|
}
|
||||||
ret := &Server{
|
return &Server{
|
||||||
mux: mux,
|
mux: mux,
|
||||||
Server: server,
|
Server: server,
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
}
|
}
|
||||||
ret.setupAdmin()
|
|
||||||
ret.setupSubsonic()
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func extendFromBox(tmpl *template.Template, box *packr.Box, key string) *templat
|
|||||||
return newT
|
return newT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupAdmin() {
|
func (s *Server) SetupAdmin() {
|
||||||
sessionKey := []byte(s.GetSetting("session_key"))
|
sessionKey := []byte(s.GetSetting("session_key"))
|
||||||
if len(sessionKey) == 0 {
|
if len(sessionKey) == 0 {
|
||||||
sessionKey = securecookie.GenerateRandomKey(32)
|
sessionKey = securecookie.GenerateRandomKey(32)
|
||||||
s.SetSetting("session_key", string(sessionKey))
|
s.SetSetting("session_key", string(sessionKey))
|
||||||
}
|
}
|
||||||
// create gormstore (and cleanup) for backend sessions
|
// create gormstore (and cleanup) for backend sessions
|
||||||
s.SessDB = gormstore.New(s.DB, []byte(sessionKey))
|
s.SessDB = gormstore.New(s.DB, sessionKey)
|
||||||
go s.SessDB.PeriodicCleanup(1*time.Hour, nil)
|
go s.SessDB.PeriodicCleanup(1*time.Hour, nil)
|
||||||
// using packr to bundle templates and static files
|
// using packr to bundle templates and static files
|
||||||
box := packr.New("templates", "./templates")
|
box := packr.New("templates", "./templates")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
func (s *Server) setupSubsonic() {
|
func (s *Server) SetupSubsonic() {
|
||||||
withWare := newChain(
|
withWare := newChain(
|
||||||
s.WithLogging,
|
s.WithLogging,
|
||||||
s.WithCORS,
|
s.WithCORS,
|
||||||
|
|||||||
Reference in New Issue
Block a user