add routes

This commit is contained in:
sentriz
2019-04-14 15:28:23 +01:00
parent 76ad2ec4eb
commit 87efb3b3c5
11 changed files with 481 additions and 285 deletions

View File

@@ -81,7 +81,7 @@ func handleFolderCompletion(fullPath string, info *godirwalk.Dirent) error {
cover := db.Cover{
Path: cLastAlbum.coverPath,
}
err := tx.Where(cover).First(&cover).Error
err := tx.Where(cover).First(&cover).Error // TODO: swap
if !gorm.IsRecordNotFoundError(err) &&
!cLastAlbum.coverModTime.After(cover.UpdatedAt) {
return nil
@@ -123,7 +123,7 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error {
track := db.Track{
Path: fullPath,
}
err = tx.Where(track).First(&track).Error
err = tx.Where(track).First(&track).Error // TODO: swap
if !gorm.IsRecordNotFoundError(err) &&
!modTime.After(track.UpdatedAt) {
return nil
@@ -136,6 +136,7 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error {
discNumber, TotalDiscs := tags.Disc()
track.Path = fullPath
track.Title = tags.Title()
track.Artist = tags.Artist()
track.DiscNumber = uint(discNumber)
track.TotalDiscs = uint(TotalDiscs)
track.TotalTracks = uint(totalTracks)
@@ -143,25 +144,26 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error {
track.Year = uint(tags.Year())
track.Suffix = extension
track.ContentType = mime
// set artist {
artist := db.Artist{
track.Size = uint(stat.Size())
// set album artist {
albumArtist := db.AlbumArtist{
Name: tags.AlbumArtist(),
}
err = tx.Where(artist).First(&artist).Error
err = tx.Where(albumArtist).First(&albumArtist).Error
if gorm.IsRecordNotFoundError(err) {
artist.Name = tags.AlbumArtist()
tx.Save(&artist)
albumArtist.Name = tags.AlbumArtist()
tx.Save(&albumArtist)
}
track.ArtistID = artist.ID
track.AlbumArtistID = albumArtist.ID
// set album
album := db.Album{
ArtistID: artist.ID,
Title: tags.Album(),
AlbumArtistID: albumArtist.ID,
Title: tags.Album(),
}
err = tx.Where(album).First(&album).Error
if gorm.IsRecordNotFoundError(err) {
album.Title = tags.Album()
album.ArtistID = artist.ID
album.AlbumArtistID = albumArtist.ID
tx.Save(&album)
}
track.AlbumID = album.ID
@@ -181,7 +183,7 @@ func main() {
orm.SetLogger(log.New(os.Stdout, "gorm ", 0))
orm.AutoMigrate(
&db.Album{},
&db.Artist{},
&db.AlbumArtist{},
&db.Track{},
&db.Cover{},
&db.User{},

View File

@@ -40,15 +40,27 @@ func main() {
mux.HandleFunc("/rest/ping.view", withWare(cont.Ping))
mux.HandleFunc("/rest/stream", withWare(cont.Stream))
mux.HandleFunc("/rest/stream.view", withWare(cont.Stream))
mux.HandleFunc("/rest/getMusicDirectory", withWare(cont.GetMusicDirectory))
mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory))
mux.HandleFunc("/rest/download", withWare(cont.Stream))
mux.HandleFunc("/rest/download.view", withWare(cont.Stream))
mux.HandleFunc("/rest/getCoverArt", withWare(cont.GetCoverArt))
mux.HandleFunc("/rest/getCoverArt.view", withWare(cont.GetCoverArt))
mux.HandleFunc("/rest/getIndexes", withWare(cont.GetIndexes))
mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes))
mux.HandleFunc("/rest/getArtists", withWare(cont.GetArtists))
mux.HandleFunc("/rest/getArtists.view", withWare(cont.GetArtists))
mux.HandleFunc("/rest/getArtist", withWare(cont.GetArtist))
mux.HandleFunc("/rest/getArtist.view", withWare(cont.GetArtist))
mux.HandleFunc("/rest/getAlbum", withWare(cont.GetAlbum))
mux.HandleFunc("/rest/getAlbum.view", withWare(cont.GetAlbum))
mux.HandleFunc("/rest/getMusicFolders", withWare(cont.GetMusicFolders))
mux.HandleFunc("/rest/getMusicFolders.view", withWare(cont.GetMusicFolders))
mux.HandleFunc("/rest/getAlbumList2", withWare(cont.GetAlbumList))
mux.HandleFunc("/rest/getAlbumList2.view", withWare(cont.GetAlbumList))
mux.HandleFunc("/rest/getLicense", withWare(cont.GetLicence))
mux.HandleFunc("/rest/getLicense.view", withWare(cont.GetLicence))
mux.HandleFunc("/", withWare(cont.NotFound))
// mux.HandleFunc("/rest/getMusicDirectory", withWare(cont.GetMusicDirectory))
// mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory))
// mux.HandleFunc("/rest/getIndexes", withWare(cont.GetIndexes))
// mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes))
server := &http.Server{
Addr: address,
Handler: mux,

View File

@@ -3,14 +3,14 @@ package db
// Album represents the albums table
type Album struct {
Base
Artist Artist
ArtistID uint
Title string `gorm:"not null;index"`
Tracks []Track
AlbumArtist AlbumArtist
AlbumArtistID uint
Title string `gorm:"not null;index"`
Tracks []Track
}
// Artist represents the artists table
type Artist struct {
// AlbumArtist represents the AlbumArtists table
type AlbumArtist struct {
Base
Albums []Album
Name string `gorm:"not null;unique_index"`
@@ -19,29 +19,31 @@ type Artist struct {
// Track represents the tracks table
type Track struct {
Base
Album Album
AlbumID uint
Artist Artist
ArtistID uint
Bitrate uint
Codec string
DiscNumber uint
Duration uint
Title string
TotalDiscs uint
TotalTracks uint
TrackNumber uint
Year uint
Suffix string
ContentType string
Path string `gorm:"not null;unique_index"`
Album Album
AlbumID uint
AlbumArtist AlbumArtist
AlbumArtistID uint
Artist string
Bitrate uint
Codec string
DiscNumber uint
Duration uint
Title string
TotalDiscs uint
TotalTracks uint
TrackNumber uint
Year uint
Suffix string
ContentType string
Size uint
Path string `gorm:"not null;unique_index"`
}
// Cover represents the covers table
type Cover struct {
Base
CrudBase
AlbumID uint `gorm:"primary_key;auto_increment:false"`
Album Album
AlbumID uint
Image []byte
Path string `gorm:"not null;unique_index"`
}

3
go.mod
View File

@@ -13,8 +13,9 @@ require (
github.com/karrick/godirwalk v1.8.0
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/mozillazg/go-unidecode v0.1.1
github.com/myesui/uuid v1.0.0 // indirect
github.com/twinj/uuid v1.0.0
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 // indirect
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
google.golang.org/appengine v1.5.0 // indirect
)

9
go.sum
View File

@@ -8,6 +8,7 @@ git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqbl
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -73,6 +74,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE=
github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
@@ -104,8 +107,8 @@ golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5a
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -139,6 +142,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -173,6 +177,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"log"
"net/http"
"strconv"
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/subsonic"
@@ -15,38 +16,60 @@ type Controller struct {
DB *gorm.DB
}
func getStrParam(r *http.Request, key string) string {
return r.URL.Query().Get(key)
}
func getIntParam(r *http.Request, key string) (int, error) {
strVal := r.URL.Query().Get(key)
if strVal == "" {
return 0, fmt.Errorf("no param with key `%s`", key)
}
val, err := strconv.Atoi(strVal)
if err != nil {
return 0, fmt.Errorf("not an int `%s`", strVal)
}
return val, nil
}
func getIntParamOr(r *http.Request, key string, or int) int {
val, err := getIntParam(r, key)
if err != nil {
return or
}
return val
}
func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.Response) {
format := r.URL.Query().Get("f")
switch format {
res := subsonic.MetaResponse{
Response: sub,
}
switch r.URL.Query().Get("f") {
case "json":
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(sub)
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
w.Write([]byte(`{"subsonic-response":`))
w.Write(data)
w.Write([]byte("}"))
fmt.Println("THE JSON", string(data))
case "jsonp":
w.Header().Set("Content-Type", "application/javascript")
data, err := json.Marshal(sub)
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
callback := r.URL.Query().Get("callback")
w.Write([]byte(fmt.Sprintf(`%s({"subsonic-response":`, callback)))
w.Write([]byte(callback))
w.Write([]byte("("))
w.Write(data)
w.Write([]byte("});"))
fmt.Println("THE JSONP", string(data))
w.Write([]byte(");"))
default:
w.Header().Set("Content-Type", "application/xml")
data, err := xml.Marshal(sub)
data, err := xml.Marshal(res)
if err != nil {
log.Printf("could not marshall to xml: %v\n", err)
}
w.Write(data)
fmt.Println("THE XML", string(data))
}
}

View File

@@ -4,179 +4,212 @@ import (
"fmt"
"net/http"
"os"
"strconv"
"unicode"
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/db"
"github.com/sentriz/gonic/subsonic"
"github.com/mozillazg/go-unidecode"
)
func (c *Controller) Ping(w http.ResponseWriter, req *http.Request) {
var orderExpr = map[string]interface{}{
"random": gorm.Expr("random()"),
"newest": "updated_at desc",
"alphabeticalByName": "title",
"alphabeticalByArtist": "album_artist.name",
}
func indexOf(s string) rune {
first := string(s[0])
c := rune(unidecode.Unidecode(first)[0])
if !unicode.IsLetter(c) {
return '#'
}
return c
}
func (c *Controller) Ping(w http.ResponseWriter, r *http.Request) {
sub := subsonic.NewResponse()
respond(w, req, sub)
respond(w, r, sub)
}
func (c *Controller) GetIndexes(w http.ResponseWriter, req *http.Request) {
var artists []db.Artist
c.DB.Find(&artists)
indexMap := make(map[byte]*subsonic.Index)
for _, artist := range artists {
first := artist.Name[0]
if !unicode.IsLetter(rune(first)) {
first = 0x23 // '#'
}
_, ok := indexMap[first]
if !ok {
indexMap[first] = &subsonic.Index{
Name: string(first),
Artists: []*subsonic.Artist{},
}
}
indexMap[first].Artists = append(
indexMap[first].Artists,
&subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
},
)
}
indexes := []*subsonic.Index{}
for _, v := range indexMap {
indexes = append(indexes, v)
}
sub := subsonic.NewResponse()
sub.Indexes = &subsonic.Indexes{
Index: &indexes,
}
respond(w, req, sub)
}
func browseArtist(c *gorm.DB, artist *db.Artist) *subsonic.Directory {
var cover db.Cover
var dir subsonic.Directory
dir.Name = artist.Name
dir.ID = artist.ID
dir.Parent = 0
var albums []*db.Album
c.Model(artist).Related(&albums)
dir.Children = make([]subsonic.Child, len(albums))
for i, album := range albums {
c.Model(album).Related(&cover)
dir.Children[i] = subsonic.Child{
Artist: artist.Name,
ID: album.ID,
IsDir: true,
Parent: artist.ID,
Title: album.Title,
CoverArt: cover.ID,
}
cover = db.Cover{}
}
return &dir
}
func browseAlbum(c *gorm.DB, album *db.Album) *subsonic.Directory {
var artist db.Artist
c.Model(album).Related(&artist)
var tracks []*db.Track
c.Model(album).Related(&tracks)
var cover db.Cover
c.Model(album).Related(&cover)
var dir subsonic.Directory
dir.Name = album.Title
dir.ID = album.ID
dir.Parent = artist.ID
dir.Children = make([]subsonic.Child, len(tracks))
for i, track := range tracks {
dir.Children[i] = subsonic.Child{
ID: track.ID,
Title: track.Title,
Parent: album.ID,
Artist: artist.Name,
ArtistID: artist.ID,
Album: album.Title,
AlbumID: album.ID,
IsDir: false,
Path: track.Path,
CoverArt: cover.ID,
ContentType: track.ContentType,
Suffix: track.Suffix,
Duration: 0,
}
}
return &dir
}
func (c *Controller) GetMusicDirectory(w http.ResponseWriter, req *http.Request) {
idStr := req.URL.Query().Get("id")
if idStr == "" {
respondError(w, req, 10, "please provide an `id` parameter")
func (c *Controller) GetCoverArt(w http.ResponseWriter, r *http.Request) {
id, err := getIntParam(r, "id")
if err != nil {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
id, _ := strconv.Atoi(idStr)
sub := subsonic.NewResponse()
var artist db.Artist
c.DB.First(&artist, id)
if artist.ID != 0 {
sub.MusicDirectory = browseArtist(c.DB, &artist)
respond(w, req, sub)
return
}
var album db.Album
c.DB.First(&album, id)
if album.ID != 0 {
sub.MusicDirectory = browseAlbum(c.DB, &album)
respond(w, req, sub)
return
}
respondError(w, req,
70, fmt.Sprintf("directory with id `%d` was not found", id),
)
}
func (c *Controller) GetCoverArt(w http.ResponseWriter, req *http.Request) {
idStr := req.URL.Query().Get("id")
if idStr == "" {
respondError(w, req, 10, "please provide an `id` parameter")
return
}
id, _ := strconv.Atoi(idStr)
var cover db.Cover
c.DB.First(&cover, id)
w.Write(cover.Image)
}
func (c *Controller) Stream(w http.ResponseWriter, req *http.Request) {
idStr := req.URL.Query().Get("id")
if idStr == "" {
respondError(w, req, 10, "please provide an `id` parameter")
func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
var artists []*db.AlbumArtist
c.DB.Find(&artists)
var indexMap = make(map[rune]*subsonic.Index)
var indexes []*subsonic.Index
for _, artist := range artists {
i := indexOf(artist.Name)
index, ok := indexMap[i]
if !ok {
index = &subsonic.Index{
Name: string(i),
Artists: []*subsonic.Artist{},
}
indexMap[i] = index
indexes = append(indexes, index)
}
index.Artists = append(index.Artists, &subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
})
}
sub := subsonic.NewResponse()
sub.Artists = indexes
respond(w, r, sub)
}
func (c *Controller) GetArtist(w http.ResponseWriter, r *http.Request) {
id, err := getIntParam(r, "id")
if err != nil {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
var artist db.AlbumArtist
c.DB.
Preload("Albums").
First(&artist, id)
sub := subsonic.NewResponse()
sub.Artist = &subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
}
for _, album := range artist.Albums {
sub.Artist.Albums = append(sub.Artist.Albums, &subsonic.Album{
ID: album.ID,
Name: album.Title,
Created: album.CreatedAt,
Artist: artist.Name,
ArtistID: artist.ID,
CoverID: album.ID,
})
}
respond(w, r, sub)
}
func (c *Controller) GetAlbum(w http.ResponseWriter, r *http.Request) {
id, err := getIntParam(r, "id")
if err != nil {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
var album db.Album
c.DB.
Preload("AlbumArtist").
Preload("Tracks").
First(&album, id)
sub := subsonic.NewResponse()
sub.Album = &subsonic.Album{
ID: album.ID,
Name: album.Title,
CoverID: album.ID,
Created: album.CreatedAt,
Artist: album.AlbumArtist.Name,
}
for _, track := range album.Tracks {
sub.Album.Tracks = append(sub.Album.Tracks, &subsonic.Track{
ID: track.ID,
Title: track.Title,
Artist: track.Artist, // track artist
TrackNo: track.TrackNumber,
ContentType: track.ContentType,
Path: track.Path,
Suffix: track.Suffix,
Created: track.CreatedAt,
Size: track.Size,
Album: album.Title,
AlbumID: album.ID,
ArtistID: album.AlbumArtist.ID, // album artist
CoverID: album.ID,
Type: "music",
})
}
respond(w, r, sub)
}
func (c *Controller) GetMusicFolders(w http.ResponseWriter, r *http.Request) {
sub := subsonic.NewResponse()
sub.MusicFolders = []*subsonic.MusicFolder{
{ID: 0, Name: "music"},
}
respond(w, r, sub)
}
func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
listType := getStrParam(r, "type")
if listType == "" {
respondError(w, r, 10, "please provide a `type` parameter")
return
}
orderType, ok := orderExpr[listType]
if !ok {
respondError(w, r, 10, fmt.Sprintf(
"unknown value `%s` for parameter 'type'", listType,
))
return
}
size := getIntParamOr(r, "size", 10)
var albums []*db.Album
c.DB.
Preload("AlbumArtist").
Order(orderType).
Limit(size).
Find(&albums)
sub := subsonic.NewResponse()
for _, album := range albums {
sub.Albums = append(sub.Albums, &subsonic.Album{
ID: album.ID,
Name: album.Title,
Created: album.CreatedAt,
CoverID: album.ID,
Artist: album.AlbumArtist.Name,
ArtistID: album.AlbumArtist.ID,
})
}
respond(w, r, sub)
}
func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) {
id, err := getIntParam(r, "id")
if err != nil {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
id, _ := strconv.Atoi(idStr)
var track db.Track
c.DB.First(&track, id)
if track.Path == "" {
respondError(w, req, 70, fmt.Sprintf("media with id `%d` was not found", id))
respondError(w, r, 70, fmt.Sprintf("media with id `%d` was not found", id))
return
}
file, err := os.Open(track.Path)
if err != nil {
respondError(w, req, 0, fmt.Sprintf("error while streaming media: %v", err))
respondError(w, r, 0, fmt.Sprintf("error while streaming media: %v", err))
return
}
stat, _ := file.Stat()
http.ServeContent(w, req, track.Path, stat.ModTime(), file)
http.ServeContent(w, r, track.Path, stat.ModTime(), file)
}
func (c *Controller) GetLicence(w http.ResponseWriter, req *http.Request) {
func (c *Controller) GetLicence(w http.ResponseWriter, r *http.Request) {
sub := subsonic.NewResponse()
sub.Licence = &subsonic.Licence{
Valid: true,
}
respond(w, req, sub)
respond(w, r, sub)
}
func (c *Controller) NotFound(w http.ResponseWriter, req *http.Request) {
respondError(w, req, 0, "unknown route")
func (c *Controller) NotFound(w http.ResponseWriter, r *http.Request) {
respondError(w, r, 0, "unknown route")
}

View File

@@ -15,14 +15,14 @@ var requiredParameters = []string{
"u", "v", "c",
}
func checkCredentialsNewWay(password, token, salt string) bool {
func checkCredentialsToken(password, token, salt string) bool {
toHash := fmt.Sprintf("%s%s", password, salt)
hash := md5.Sum([]byte(toHash))
expToken := hex.EncodeToString(hash[:])
return token == expToken
}
func checkCredentialsOldWay(password, givenPassword string) bool {
func checkCredentialsBasic(password, givenPassword string) bool {
if givenPassword[:4] == "enc:" {
bytes, _ := hex.DecodeString(givenPassword[4:])
givenPassword = string(bytes)
@@ -71,9 +71,9 @@ func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc {
}
var credsOk bool
if tokenAuth {
credsOk = checkCredentialsNewWay(user.Password, token, salt)
credsOk = checkCredentialsToken(user.Password, token, salt)
} else {
credsOk = checkCredentialsOldWay(user.Password, password)
credsOk = checkCredentialsBasic(user.Password, password)
}
if !credsOk {
respondError(w, r, 40, "invalid password")

119
handler/oldfolderbase Normal file
View File

@@ -0,0 +1,119 @@
// func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
// var artists []*db.Artist
// c.DB.Find(&artists)
// indexMap := make(map[byte]*subsonic.Index)
// for _, artist := range artists {
// first := artist.Name[0]
// if !unicode.IsLetter(rune(first)) {
// first = 0x23 // '#'
// }
// _, ok := indexMap[first]
// if !ok {
// indexMap[first] = &subsonic.Index{
// Name: string(first),
// Artists: []*subsonic.Artist{},
// }
// }
// indexMap[first].Artists = append(
// indexMap[first].Artists,
// &subsonic.Artist{
// ID: artist.ID,
// Name: artist.Name,
// },
// )
// }
// indexes := []*subsonic.Index{}
// for _, v := range indexMap {
// indexes = append(indexes, v)
// }
// sub := subsonic.NewResponse()
// sub.Indexes = &subsonic.Indexes{
// Index: &indexes,
// }
// respond(w, r, sub)
// }
// func browseArtist(c *gorm.DB, artist *db.Artist) *subsonic.Directory {
// var cover db.Cover
// var dir subsonic.Directory
// dir.Name = artist.Name
// dir.ID = artist.ID
// dir.Parent = 0
// var albums []*db.Album
// c.Model(artist).Related(&albums)
// dir.Children = make([]subsonic.Child, len(albums))
// for i, album := range albums {
// c.Model(album).Related(&cover)
// dir.Children[i] = subsonic.Child{
// Artist: artist.Name,
// ID: album.ID,
// IsDir: true,
// Parent: artist.ID,
// Title: album.Title,
// CoverID: cover.AlbumID,
// }
// cover = db.Cover{}
// }
// return &dir
// }
// func browseAlbum(c *gorm.DB, album *db.Album) *subsonic.Directory {
// var artist db.Artist
// c.Model(album).Related(&artist)
// var tracks []*db.Track
// c.Model(album).Related(&tracks)
// var cover db.Cover
// c.Model(album).Related(&cover)
// var dir subsonic.Directory
// dir.Name = album.Title
// dir.ID = album.ID
// dir.Parent = artist.ID
// dir.Children = make([]subsonic.Child, len(tracks))
// for i, track := range tracks {
// dir.Children[i] = subsonic.Child{
// ID: track.ID,
// Title: track.Title,
// Parent: album.ID,
// Artist: artist.Name,
// ArtistID: artist.ID,
// Album: album.Title,
// AlbumID: album.ID,
// IsDir: false,
// Path: track.Path,
// CoverArt: cover.ID,
// ContentType: track.ContentType,
// Suffix: track.Suffix,
// Duration: 0,
// }
// }
// return &dir
// }
// func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
// idStr := r.URL.Query().Get("id")
// if idStr == "" {
// respondError(w, r, 10, "please provide an `id` parameter")
// return
// }
// id, _ := strconv.Atoi(idStr)
// sub := subsonic.NewResponse()
// var artist db.Artist
// c.DB.First(&artist, id)
// if artist.ID != 0 {
// sub.Directory = browseArtist(c.DB, &artist)
// respond(w, r, sub)
// return
// }
// var album db.Album
// c.DB.First(&album, id)
// if album.ID != 0 {
// sub.Directory = browseAlbum(c.DB, &album)
// respond(w, r, sub)
// return
// }
// respondError(w, r,
// 70, fmt.Sprintf("directory with id `%d` was not found", id),
// )
// }

View File

@@ -1,101 +1,98 @@
package subsonic
import "encoding/xml"
import "time"
type Album struct {
XMLName xml.Name `xml:"album" json:"-"`
ID uint `xml:"id,attr" json:"id"`
Name string `xml:"name,attr" json:"name"`
ArtistID uint `xml:"artistId,attr" json:"artistId"`
ArtistName string `xml:"artist,attr" json:"artist"`
SongCount uint `xml:"songCount,attr" json:"songCount"`
Duration uint `xml:"duration,attr" json:"duration"`
CoverArt string `xml:"coverArt,attr" json:"coverArt"`
Created string `xml:"created,attr" json:"created"`
Songs *[]*Song `xml:"song" json:"song,omitempty"`
ID uint `xml:"id,attr" json:"id"`
Name string `xml:"name,attr" json:"name"`
ArtistID uint `xml:"artistId,attr" json:"artistId"`
Artist string `xml:"artist,attr" json:"artist"`
TrackCount uint `xml:"songCount,attr" json:"songCount"`
Duration uint `xml:"duration,attr" json:"duration"`
CoverID uint `xml:"coverArt,attr" json:"coverArt"`
Created time.Time `xml:"created,attr" json:"created"`
Tracks []*Track `xml:"song" json:"song,omitempty"`
}
type RandomSongs struct {
XMLName xml.Name `xml:"randomSongs" json:"-"`
Songs []*Song `xml:"song" json:"song"`
type RandomTracks struct {
Tracks []*Track `xml:"song" json:"song"`
}
type Song struct {
XMLName xml.Name `xml:"song" json:"-"`
ID uint `xml:"id,attr" json:"id"`
Parent uint `xml:"parent,attr" json:"parent"`
Title string `xml:"title,attr" json:"title"`
Album string `xml:"album,attr" json:"album"`
Artist string `xml:"artist,attr" json:"artist"`
IsDir bool `xml:"isDir,attr" json:"isDir"`
CoverArt string `xml:"coverArt,attr" json:"coverArt"`
Created string `xml:"created,attr" json:"created"`
Duration uint `xml:"duration,attr" json:"duration"`
Genre string `xml:"genre,attr" json:"genre"`
BitRate uint `xml:"bitRate,attr" json:"bitRate"`
Size uint `xml:"size,attr" json:"size"`
Suffix string `xml:"suffix,attr" json:"suffix"`
ContentType string `xml:"contentType,attr" json:"contentType"`
IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
Path string `xml:"path,attr" json:"path"`
AlbumID uint `xml:"albumId,attr" json:"albumId"`
ArtistID uint `xml:"artistId,attr" json:"artistId"`
TrackNo uint `xml:"track,attr" json:"track"`
Type string `xml:"type,attr" json:"type"`
type Track struct {
ID uint `xml:"id,attr" json:"id"`
Parent uint `xml:"parent,attr" json:"parent"`
Title string `xml:"title,attr" json:"title"`
Album string `xml:"album,attr" json:"album"`
Artist string `xml:"artist,attr" json:"artist"`
IsDir bool `xml:"isDir,attr" json:"isDir"`
CoverID uint `xml:"coverArt,attr" json:"coverArt"`
Created time.Time `xml:"created,attr" json:"created"`
Duration uint `xml:"duration,attr" json:"duration"`
Genre string `xml:"genre,attr" json:"genre"`
BitRate uint `xml:"bitRate,attr" json:"bitRate"`
Size uint `xml:"size,attr" json:"size"`
Suffix string `xml:"suffix,attr" json:"suffix"`
ContentType string `xml:"contentType,attr" json:"contentType"`
IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
Path string `xml:"path,attr" json:"path"`
AlbumID uint `xml:"albumId,attr" json:"albumId"`
ArtistID uint `xml:"artistId,attr" json:"artistId"`
TrackNo uint `xml:"track,attr" json:"track"`
Type string `xml:"type,attr" json:"type"`
}
type Artist struct {
XMLName xml.Name `xml:"artist" json:"-"`
ID uint `xml:"id,attr" json:"id"`
Name string `xml:"name,attr" json:"name"`
CoverArt string `xml:"coverArt,attr" json:"coverArt,omitempty"`
CoverID uint `xml:"coverArt,attr" json:"coverArt,omitempty"`
AlbumCount uint `xml:"albumCount,attr" json:"albumCount,omitempty"`
Albums []*Album `xml:"album,omitempty" json:"album,omitempty"`
}
type Indexes struct {
LastModified uint `xml:"lastModified,attr" json:"lastModified"`
Index *[]*Index `xml:"index" json:"index"`
LastModified uint `xml:"lastModified,attr" json:"lastModified"`
Index []*Index `xml:"index" json:"index"`
}
type Index struct {
XMLName xml.Name `xml:"index" json:"-"`
Name string `xml:"name,attr" json:"name"`
Artists []*Artist `xml:"artist" json:"artist"`
}
type Directory struct {
XMLName xml.Name `xml:"directory" json:"-"`
ID uint `xml:"id,attr" json:"id"`
Parent uint `xml:"parent,attr" json:"parent"`
Name string `xml:"name,attr" json:"name"`
Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"`
Children []Child `xml:"child" json:"child"`
ID uint `xml:"id,attr" json:"id"`
Parent uint `xml:"parent,attr" json:"parent"`
Name string `xml:"name,attr" json:"name"`
Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"`
Children []Child `xml:"child" json:"child"`
}
type Child struct {
XMLName xml.Name `xml:"child" json:"-"`
ID uint `xml:"id,attr" json:"id,omitempty"`
Parent uint `xml:"parent,attr" json:"parent,omitempty"`
Title string `xml:"title,attr" json:"title,omitempty"`
IsDir bool `xml:"isDir,attr" json:"isDir,omitempty"`
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID uint `xml:"albumId,attr,omitempty" json:"albumId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
ArtistID uint `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Track uint `xml:"track,attr,omitempty" json:"track,omitempty"`
Year uint `xml:"year,attr,omitempty" json:"year,omitempty"`
Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"`
CoverArt uint `xml:"coverart,attr" json:"coverArt,omitempty"`
Size uint `xml:"size,attr,omitempty" json:"size,omitempty"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"`
Duration uint `xml:"duration,attr,omitempty" json:"duration"`
BitRate uint `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
ID uint `xml:"id,attr" json:"id,omitempty"`
Parent uint `xml:"parent,attr" json:"parent,omitempty"`
Title string `xml:"title,attr" json:"title,omitempty"`
IsDir bool `xml:"isDir,attr" json:"isDir,omitempty"`
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID uint `xml:"albumId,attr,omitempty" json:"albumId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
ArtistID uint `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Track uint `xml:"track,attr,omitempty" json:"track,omitempty"`
Year uint `xml:"year,attr,omitempty" json:"year,omitempty"`
Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"`
CoverID uint `xml:"coverArt,attr" json:"coverArt,omitempty"`
Size uint `xml:"size,attr,omitempty" json:"size,omitempty"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"`
Duration uint `xml:"duration,attr,omitempty" json:"duration"`
BitRate uint `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
}
type MusicFolder struct {
ID uint `xml:"id,attr" json:"id,omitempty"`
Name string `xml:"name,attr" json:"name,omitempty"`
}
type Licence struct {
XMLName xml.Name `xml:"license" json:"-"`
Valid bool `xml:"valid,attr,omitempty" json:"valid,omitempty"`
Valid bool `xml:"valid,attr,omitempty" json:"valid,omitempty"`
}

View File

@@ -2,36 +2,38 @@
package subsonic
import (
"encoding/xml"
)
import "encoding/xml"
var (
apiVersion = "1.9.0"
xmlns = "http://subsonic.org/restapi"
)
type MetaResponse struct {
XMLName xml.Name `xml:"subsonic-response" json:"-"`
*Response `json:"subsonic-response"`
}
type Response struct {
XMLName xml.Name `xml:"subsonic-response" json:"-"`
Status string `xml:"status,attr" json:"status"`
Version string `xml:"version,attr" json:"version"`
XMLNS string `xml:"xmlns,attr" json:"-"`
Error *Error `xml:"error" json:"error,omitempty"`
AlbumList2 *[]*Album `xml:"albumList2>album" json:"album,omitempty"`
Album *Album `xml:"album" json:"album,omitempty"`
Song *Song `xml:"song" json:"song,omitempty"`
Indexes *Indexes `xml:"indexes" json:"indexes,omitempty"`
Artists *[]*Index `xml:"artists>index" json:"artists,omitempty"`
Artist *Artist `xml:"artist" json:"artist,omitempty"`
MusicDirectory *Directory `xml:"directory" json:"directory,omitempty"`
RandomSongs *RandomSongs `xml:"randomSongs" json:"randomSongs,omitempty"`
Licence *Licence `xml:"license" json:"license,omitempty"`
Status string `xml:"status,attr" json:"status"`
Version string `xml:"version,attr" json:"version"`
XMLNS string `xml:"xmlns,attr" json:"-"`
Error *Error `xml:"error" json:"error,omitempty"`
Albums []*Album `xml:"albumList2>album" json:"albumList2,omitempty"`
Album *Album `xml:"album" json:"album,omitempty"`
Track *Track `xml:"song" json:"song,omitempty"`
Indexes *Indexes `xml:"indexes" json:"indexes,omitempty"`
Artists []*Index `xml:"artists>index" json:"artists,omitempty"`
Artist *Artist `xml:"artist" json:"artist,omitempty"`
Directory *Directory `xml:"directory" json:"directory,omitempty"`
RandomTracks *RandomTracks `xml:"randomSongs" json:"randomSongs,omitempty"`
MusicFolders []*MusicFolder `xml:"musicFolders>musicFolder" json:"musicFolders,omitempty"`
Licence *Licence `xml:"license" json:"license,omitempty"`
}
type Error struct {
XMLName xml.Name `xml:"error" json:"-"`
Code uint64 `xml:"code,attr" json:"code"`
Message string `xml:"message,attr" json:"message"`
Code uint64 `xml:"code,attr" json:"code"`
Message string `xml:"message,attr" json:"message"`
}
func NewResponse() *Response {