construct subsonic models elsewhere

This commit is contained in:
sentriz
2019-05-28 14:22:44 +01:00
parent 6eb1041ad8
commit 74c55bd509
13 changed files with 243 additions and 192 deletions

View File

@@ -13,9 +13,9 @@ import "time"
type Album struct {
IDBase
CrudBase
AlbumArtist AlbumArtist
AlbumArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES album_artists(id) ON DELETE CASCADE"`
Title string `gorm:"not null; index"`
Artist Artist
ArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
Title string `gorm:"not null; index"`
// an Album having a `Path` is a little weird when browsing by tags
// (for the most part - the library's folder structure is treated as
// if it were flat), but this solves the "American Football problem"
@@ -28,8 +28,8 @@ type Album struct {
IsNew bool `gorm:"-"`
}
// AlbumArtist represents the AlbumArtists table
type AlbumArtist struct {
// Artist represents the Artists table
type Artist struct {
IDBase
CrudBase
Name string `gorm:"not null; unique_index"`
@@ -40,26 +40,26 @@ type AlbumArtist struct {
type Track struct {
IDBase
CrudBase
Album Album
AlbumID int `gorm:"index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
AlbumArtist AlbumArtist
AlbumArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES album_artists(id) ON DELETE CASCADE"`
Artist string
Bitrate int
Codec string
DiscNumber int
Duration int
Title string
TotalDiscs int
TotalTracks int
TrackNumber int
Year int
Suffix string
ContentType string
Size int
Folder Folder
FolderID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES folders(id) ON DELETE CASCADE"`
Path string `gorm:"not null; unique_index"`
Album Album
AlbumID int `gorm:"index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Artist Artist
ArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
TrackArtist string
Bitrate int
Codec string
DiscNumber int
Duration int
Title string
TotalDiscs int
TotalTracks int
TrackNumber int
Year int
Suffix string
ContentType string
Size int
Folder Folder
FolderID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES folders(id) ON DELETE CASCADE"`
Path string `gorm:"not null; unique_index"`
}
// Cover represents the covers table

View File

@@ -31,7 +31,7 @@ type Scanner struct {
curTracks []model.Track
curCover model.Cover
curAlbum model.Album
curAArtist model.AlbumArtist
curAArtist model.Artist
}
func New(db *gorm.DB, musicPath string) *Scanner {
@@ -43,7 +43,7 @@ func New(db *gorm.DB, musicPath string) *Scanner {
curTracks: make([]model.Track, 0),
curCover: model.Cover{},
curAlbum: model.Album{},
curAArtist: model.AlbumArtist{},
curAArtist: model.Artist{},
}
}
@@ -105,7 +105,7 @@ func (s *Scanner) MigrateDB() error {
defer s.tx.Commit()
s.tx.AutoMigrate(
model.Album{},
model.AlbumArtist{},
model.Artist{},
model.Track{},
model.Cover{},
model.User{},

View File

@@ -81,7 +81,7 @@ func (s *Scanner) callbackPost(path string, info *godirwalk.Dirent) error {
s.curTracks = make([]model.Track, 0)
s.curCover = model.Cover{}
s.curAlbum = model.Album{}
s.curAArtist = model.AlbumArtist{}
s.curAArtist = model.Artist{}
//
log.Printf("processed folder `%s`\n", path)
return nil
@@ -156,7 +156,7 @@ func (s *Scanner) handleTrack(it *item) error {
track.ContentType = it.track.mime
track.Size = int(it.stat.Size())
track.Title = tags.Title()
track.Artist = tags.Artist()
track.TrackArtist = tags.Artist()
track.Year = tags.Year()
track.FolderID = s.curFolders.PeekID()
//
@@ -168,7 +168,7 @@ func (s *Scanner) handleTrack(it *item) error {
s.curAArtist.Name = tags.AlbumArtist()
s.tx.Save(&s.curAArtist)
}
track.AlbumArtistID = s.curAArtist.ID
track.ArtistID = s.curAArtist.ID
//
// set album if this is the first track in the folder
if len(s.curTracks) > 0 {
@@ -189,7 +189,7 @@ func (s *Scanner) handleTrack(it *item) error {
s.curAlbum.Path = directory
s.curAlbum.Title = tags.Album()
s.curAlbum.Year = tags.Year()
s.curAlbum.AlbumArtistID = s.curAArtist.ID
s.curAlbum.ArtistID = s.curAArtist.ID
s.curAlbum.IsNew = true
return nil
}

View File

@@ -0,0 +1,54 @@
package handler
import (
"github.com/sentriz/gonic/model"
"github.com/sentriz/gonic/server/subsonic"
)
func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Child {
return &subsonic.Child{
ID: f.ID,
Title: f.Name,
CoverID: f.CoverID,
ParentID: parent.ID,
IsDir: true,
}
}
func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Child {
return &subsonic.Child{
ID: t.ID,
Album: t.Album.Title,
Artist: t.TrackArtist,
ContentType: t.ContentType,
Path: t.Path,
Size: t.Size,
Suffix: t.Suffix,
Title: t.Title,
Track: t.TrackNumber,
ParentID: parent.ID,
CoverID: parent.CoverID,
Duration: 0,
IsDir: false,
Type: "music",
}
}
func makeAlbumFromFolder(f *model.Folder) *subsonic.Album {
return &subsonic.Album{
ID: f.ID,
Title: f.Name,
Album: f.Name,
CoverID: f.CoverID,
ParentID: f.ParentID,
Artist: f.Parent.Name,
IsDir: true,
}
}
func makeArtistFromFolder(f *model.Folder) *subsonic.Artist {
return &subsonic.Artist{
ID: f.ID,
Name: f.Name,
}
}

View File

@@ -0,0 +1,43 @@
package handler
import (
"github.com/sentriz/gonic/model"
"github.com/sentriz/gonic/server/subsonic"
)
func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
return &subsonic.Album{
ID: a.ID,
Name: a.Title,
Created: a.CreatedAt,
CoverID: a.CoverID,
Artist: artist.Name,
ArtistID: artist.ID,
}
}
func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track {
return &subsonic.Track{
ID: t.ID,
Title: t.Title,
Artist: t.TrackArtist,
TrackNumber: t.TrackNumber,
ContentType: t.ContentType,
Path: t.Path,
Suffix: t.Suffix,
CreatedAt: t.CreatedAt,
Size: t.Size,
Album: album.Title,
AlbumID: album.ID,
ArtistID: album.Artist.ID,
CoverID: album.CoverID,
Type: "music",
}
}
func makeArtistFromArtist(a *model.Artist) *subsonic.Artist {
return &subsonic.Artist{
ID: a.ID,
Name: a.Name,
}
}

View File

@@ -49,7 +49,7 @@ func (c *Controller) ServeLogout(w http.ResponseWriter, r *http.Request) {
func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
var data templateData
c.DB.Table("album_artists").Count(&data.ArtistCount)
c.DB.Table("artists").Count(&data.ArtistCount)
c.DB.Table("albums").Count(&data.AlbumCount)
c.DB.Table("tracks").Count(&data.TrackCount)
c.DB.Find(&data.AllUsers)

View File

@@ -13,7 +13,7 @@ import (
func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
// we are browsing by folder, but the subsonic docs show sub <artist> elements
// for this, so we're going to return root directories as "artists"
var folders []*model.Folder
var folders []model.Folder
c.DB.Where("parent_id = ?", 1).Find(&folders)
var indexMap = make(map[rune]*subsonic.Index)
var indexes []*subsonic.Index
@@ -28,10 +28,8 @@ func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
indexMap[i] = index
indexes = append(indexes, index)
}
index.Artists = append(index.Artists, &subsonic.Artist{
ID: folder.ID,
Name: folder.Name,
})
index.Artists = append(index.Artists,
makeArtistFromFolder(&folder))
}
sub := subsonic.NewResponse()
sub.Indexes = &subsonic.Indexes{
@@ -48,61 +46,42 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
return
}
childrenObj := []*subsonic.Child{}
var cFolder model.Folder
c.DB.First(&cFolder, id)
var folder model.Folder
c.DB.First(&folder, id)
//
// start looking for child folders in the current dir
var folders []*model.Folder
// start looking for child childFolders in the current dir
var childFolders []model.Folder
c.DB.
Where("parent_id = ?", id).
Find(&folders)
for _, folder := range folders {
childrenObj = append(childrenObj, &subsonic.Child{
Parent: cFolder.ID,
ID: folder.ID,
Title: folder.Name,
IsDir: true,
CoverID: folder.CoverID,
})
Find(&childFolders)
for _, c := range childFolders {
childrenObj = append(childrenObj,
makeChildFromFolder(&c, &folder))
}
//
// start looking for child tracks in the current dir
var tracks []*model.Track
// start looking for child childTracks in the current dir
var childTracks []model.Track
c.DB.
Where("folder_id = ?", id).
Preload("Album").
Order("title").
Find(&tracks)
for _, track := range tracks {
Find(&childTracks)
for _, c := range childTracks {
if getStrParam(r, "c") == "Jamstash" {
// jamstash thinks it can't play flacs
track.ContentType = "audio/mpeg"
track.Suffix = "mp3"
c.ContentType = "audio/mpeg"
c.Suffix = "mp3"
}
childrenObj = append(childrenObj, &subsonic.Child{
ID: track.ID,
Album: track.Album.Title,
Artist: track.Artist,
ContentType: track.ContentType,
CoverID: cFolder.CoverID,
Duration: 0,
IsDir: false,
Parent: cFolder.ID,
Path: track.Path,
Size: track.Size,
Suffix: track.Suffix,
Title: track.Title,
Track: track.TrackNumber,
Type: "music",
})
childrenObj = append(childrenObj,
makeChildFromTrack(&c, &folder))
}
//
// respond section
sub := subsonic.NewResponse()
sub.Directory = &subsonic.Directory{
ID: cFolder.ID,
Parent: cFolder.ParentID,
Name: cFolder.Name,
ID: folder.ID,
Parent: folder.ParentID,
Name: folder.Name,
Children: childrenObj,
}
respond(w, r, sub)
@@ -152,28 +131,18 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
))
return
}
var folders []*model.Folder
var folders []model.Folder
q.
Where("folders.has_tracks = 1").
Offset(getIntParamOr(r, "offset", 0)).
Limit(getIntParamOr(r, "size", 10)).
Preload("Parent").
Find(&folders)
listObj := []*subsonic.Album{}
for _, folder := range folders {
listObj = append(listObj, &subsonic.Album{
ID: folder.ID,
Title: folder.Name,
Album: folder.Name,
CoverID: folder.CoverID,
ParentID: folder.ParentID,
IsDir: true,
Artist: folder.Parent.Name,
})
}
sub := subsonic.NewResponse()
sub.Albums = &subsonic.Albums{
List: listObj,
sub.Albums = &subsonic.Albums{}
for _, folder := range folders {
sub.Albums.List = append(sub.Albums.List,
makeAlbumFromFolder(&folder))
}
respond(w, r, sub)
}

View File

@@ -11,7 +11,7 @@ import (
)
func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
var artists []*model.AlbumArtist
var artists []model.Artist
c.DB.Find(&artists)
var indexMap = make(map[rune]*subsonic.Index)
var indexes subsonic.Artists
@@ -26,10 +26,8 @@ func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
indexMap[i] = index
indexes.List = append(indexes.List, index)
}
index.Artists = append(index.Artists, &subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
})
index.Artists = append(index.Artists,
makeArtistFromArtist(&artist))
}
sub := subsonic.NewResponse()
sub.Artists = &indexes
@@ -42,26 +40,15 @@ func (c *Controller) GetArtist(w http.ResponseWriter, r *http.Request) {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
var artist model.AlbumArtist
var artist model.Artist
c.DB.
Preload("Albums").
First(&artist, id)
albumsObj := []*subsonic.Album{}
for _, album := range artist.Albums {
albumsObj = append(albumsObj, &subsonic.Album{
ID: album.ID,
Name: album.Title,
Created: album.CreatedAt,
Artist: artist.Name,
ArtistID: artist.ID,
CoverID: album.CoverID,
})
}
sub := subsonic.NewResponse()
sub.Artist = &subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
Albums: albumsObj,
sub.Artist = makeArtistFromArtist(&artist)
for _, album := range artist.Albums {
sub.Artist.Albums = append(sub.Artist.Albums,
makeAlbumFromAlbum(&album, &artist))
}
respond(w, r, sub)
}
@@ -73,39 +60,22 @@ func (c *Controller) GetAlbum(w http.ResponseWriter, r *http.Request) {
return
}
var album model.Album
c.DB.
Preload("AlbumArtist").
err = c.DB.
Preload("Artist").
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
return db.Order("tracks.track_number")
}).
First(&album, id)
tracksObj := []*subsonic.Track{}
for _, track := range album.Tracks {
tracksObj = append(tracksObj, &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.CoverID,
Type: "music",
})
return db.Order("tracks.track_number")
}).
First(&album, id).
Error
if gorm.IsRecordNotFoundError(err) {
respondError(w, r, 10, "couldn't find an album with that id")
return
}
sub := subsonic.NewResponse()
sub.Album = &subsonic.Album{
ID: album.ID,
Name: album.Title,
CoverID: album.CoverID,
Created: album.CreatedAt,
Artist: album.AlbumArtist.Name,
Tracks: tracksObj,
sub.Album = makeAlbumFromAlbum(&album, &album.Artist)
for _, track := range album.Tracks {
sub.Album.Tracks = append(sub.Album.Tracks,
makeTrackFromTrack(&track, &album))
}
respond(w, r, sub)
}
@@ -122,9 +92,9 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
switch listType {
case "alphabeticalByArtist":
q = q.Joins(`
JOIN album_artists
ON albums.album_artist_id = album_artists.id`)
q = q.Order("album_artists.name")
JOIN artists
ON albums.artist_id = artists.id`)
q = q.Order("artists.name")
case "alphabeticalByName":
q = q.Order("title")
case "byYear":
@@ -157,26 +127,17 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
))
return
}
var albums []*model.Album
var albums []model.Album
q.
Offset(getIntParamOr(r, "offset", 0)).
Limit(getIntParamOr(r, "size", 10)).
Preload("AlbumArtist").
Preload("Artist").
Find(&albums)
listObj := []*subsonic.Album{}
for _, album := range albums {
listObj = append(listObj, &subsonic.Album{
ID: album.ID,
Name: album.Title,
Created: album.CreatedAt,
CoverID: album.CoverID,
Artist: album.AlbumArtist.Name,
ArtistID: album.AlbumArtist.ID,
})
}
sub := subsonic.NewResponse()
sub.AlbumsTwo = &subsonic.Albums{
List: listObj,
sub.AlbumsTwo = &subsonic.Albums{}
for _, album := range albums {
sub.AlbumsTwo.List = append(sub.AlbumsTwo.List,
makeAlbumFromAlbum(&album, &album.Artist))
}
respond(w, r, sub)
}

View File

@@ -96,14 +96,14 @@ func (c *Controller) Scrobble(w http.ResponseWriter, r *http.Request) {
// fetch user to get lastfm session
user := r.Context().Value(contextUserKey).(*model.User)
if user.LastFMSession == "" {
respondError(w, r, 0, fmt.Sprintf("no last.fm session for this user: %v", err))
respondError(w, r, 0, "you don't have a last.fm session")
return
}
// fetch track for getting info to send to last.fm function
var track model.Track
c.DB.
Preload("Album").
Preload("AlbumArtist").
Preload("Artist").
First(&track, id)
// scrobble with above info
err = lastfm.Scrobble(

View File

@@ -33,12 +33,12 @@ func checkCredentialsToken(password, token, salt string) bool {
return token == expToken
}
func checkCredentialsBasic(password, givenPassword string) bool {
if givenPassword[:4] == "enc:" {
bytes, _ := hex.DecodeString(givenPassword[4:])
givenPassword = string(bytes)
func checkCredentialsBasic(password, given string) bool {
if given[:4] == "enc:" {
bytes, _ := hex.DecodeString(given[4:])
given = string(bytes)
}
return password == givenPassword
return password == given
}
func (c *Controller) WithValidSubsonicArgs(next http.HandlerFunc) http.HandlerFunc {
@@ -62,7 +62,6 @@ func (c *Controller) WithValidSubsonicArgs(next http.HandlerFunc) http.HandlerFu
}
user := c.GetUserFromName(username)
if user == nil {
// the user does not exist
respondError(w, r, 40, "invalid username")
return
}

View File

@@ -48,10 +48,10 @@ func Scrobble(apiKey, secret, session string, track *model.Track,
}
params.Add("api_key", apiKey)
params.Add("sk", session)
params.Add("artist", track.Artist)
params.Add("artist", track.TrackArtist)
params.Add("track", track.Title)
params.Add("album", track.Album.Title)
params.Add("albumArtist", track.AlbumArtist.Name)
params.Add("albumArtist", track.Artist.Name)
params.Add("trackNumber", strconv.Itoa(track.TrackNumber))
params.Add("api_sig", getParamSignature(params, secret))
_, err := makeRequest("POST", params)

View File

@@ -0,0 +1,23 @@
package lastfm
import (
"crypto/md5"
"fmt"
"net/url"
"testing"
)
func TestGetParamSignature(t *testing.T) {
params := url.Values{}
params.Add("ccc", "CCC")
params.Add("bbb", "BBB")
params.Add("aaa", "AAA")
params.Add("ddd", "DDD")
actual := getParamSignature(params, "secret")
expected := fmt.Sprintf("%x", md5.Sum([]byte(
"aaaAAAbbbBBBcccCCCdddDDDsecret",
)))
if actual != expected {
t.Errorf("expected %x, got %s", expected, actual)
}
}

View File

@@ -1,6 +1,8 @@
package subsonic
import "time"
import (
"time"
)
type Albums struct {
List []*Album `xml:"album" json:"album,omitempty"`
@@ -30,26 +32,26 @@ type RandomTracks struct {
}
type Track struct {
ID int `xml:"id,attr,omitempty" json:"id"`
Parent int `xml:"parent,attr,omitempty" json:"parent"`
Title string `xml:"title,attr,omitempty" json:"title"`
Album string `xml:"album,attr,omitempty" json:"album"`
Artist string `xml:"artist,attr,omitempty" json:"artist"`
IsDir bool `xml:"isDir,attr,omitempty" json:"isDir"`
CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt"`
Created time.Time `xml:"created,attr,omitempty" json:"created"`
Duration int `xml:"duration,attr,omitempty" json:"duration"`
Genre string `xml:"genre,attr,omitempty" json:"genre"`
Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate"`
Size int `xml:"size,attr,omitempty" json:"size"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType"`
IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo"`
Path string `xml:"path,attr,omitempty" json:"path"`
AlbumID int `xml:"albumId,attr,omitempty" json:"albumId"`
ArtistID int `xml:"artistId,attr,omitempty" json:"artistId"`
TrackNo int `xml:"track,attr,omitempty" json:"track"`
Type string `xml:"type,attr,omitempty" json:"type"`
ID int `xml:"id,attr,omitempty" json:"id"`
Parent int `xml:"parent,attr,omitempty" json:"parent"`
Title string `xml:"title,attr,omitempty" json:"title"`
Album string `xml:"album,attr,omitempty" json:"album"`
Artist string `xml:"artist,attr,omitempty" json:"artist"`
IsDir bool `xml:"isDir,attr,omitempty" json:"isDir"`
CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt"`
CreatedAt time.Time `xml:"created,attr,omitempty" json:"created"`
Duration int `xml:"duration,attr,omitempty" json:"duration"`
Genre string `xml:"genre,attr,omitempty" json:"genre"`
Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate"`
Size int `xml:"size,attr,omitempty" json:"size"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType"`
IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo"`
Path string `xml:"path,attr,omitempty" json:"path"`
AlbumID int `xml:"albumId,attr,omitempty" json:"albumId"`
ArtistID int `xml:"artistId,attr,omitempty" json:"artistId"`
TrackNumber int `xml:"track,attr,omitempty" json:"track"`
Type string `xml:"type,attr,omitempty" json:"type"`
}
type Artists struct {
@@ -84,7 +86,7 @@ type Directory struct {
type Child struct {
ID int `xml:"id,attr,omitempty" json:"id,omitempty"`
Parent int `xml:"parent,attr,omitempty" json:"parent,omitempty"`
ParentID int `xml:"parent,attr,omitempty" json:"parent,omitempty"`
Title string `xml:"title,attr,omitempty" json:"title,omitempty"`
IsDir bool `xml:"isDir,attr,omitempty" json:"isDir,omitempty"`
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`