use references when possible

This commit is contained in:
sentriz
2019-06-05 17:34:01 +01:00
parent 406b133713
commit 146f157782
11 changed files with 128 additions and 115 deletions

View File

@@ -8,14 +8,11 @@ issues:
exclude-rules: exclude-rules:
- path: _test\.go - path: _test\.go
linters: linters:
- gocyclo
- errcheck - errcheck
- dupl
- gosec
- text: "weak cryptographic primitive" - text: "weak cryptographic primitive"
linters: linters:
- gosec - gosec
- path: model/model.go - path: model/model\.go
linters: linters:
- lll - lll
- path: server/handler/ - path: server/handler/

View File

@@ -9,17 +9,17 @@ import (
type Artist struct { type Artist struct {
IDBase IDBase
Name string `gorm:"not null; unique_index"` Name string `gorm:"not null; unique_index"`
Albums []Album `gorm:"foreignkey:TagArtistID"` Albums []*Album `gorm:"foreignkey:TagArtistID"`
} }
type Track struct { type Track struct {
IDBase IDBase
CrudBase CrudBase
Album Album Album *Album
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(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"`
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"`
@@ -63,9 +63,9 @@ 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"`
Album Album Album *Album
AlbumID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES albums(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
@@ -79,10 +79,10 @@ type Album struct {
Parent *Album Parent *Album
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"` ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Cover string `sql:"default: null"` Cover string `sql:"default: null"`
TagArtist Artist TagArtist *Artist
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"` TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
TagTitle string `gorm:"index" sql:"default: null"` TagTitle string `gorm:"index" sql:"default: null"`
TagYear int `sql:"default: null"` TagYear int `sql:"default: null"`
Tracks []Track Tracks []*Track
IsNew bool `gorm:"-"` IsNew bool `gorm:"-"`
} }

View File

@@ -58,7 +58,7 @@ func (s *Scanner) callbackPost(fullPath string, info *godirwalk.Dirent) error {
if folder.IsNew { if folder.IsNew {
folder.ParentID = s.curFolderID() folder.ParentID = s.curFolderID()
folder.Cover = s.curCover folder.Cover = s.curCover
s.tx.Save(&folder) s.tx.Save(folder)
} }
s.curCover = "" s.curCover = ""
log.Printf("processed folder `%s`\n", fullPath) log.Printf("processed folder `%s`\n", fullPath)
@@ -66,42 +66,45 @@ 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.Album folder := &model.Album{}
defer s.curFolders.Push(folder)
err := s.tx. err := s.tx.
Where(model.Album{ Where(model.Album{
LeftPath: it.directory, LeftPath: it.directory,
RightPath: it.filename, RightPath: it.filename,
}). }).
First(&folder). First(folder).
Error Error
if !gorm.IsRecordNotFoundError(err) && if !gorm.IsRecordNotFoundError(err) &&
it.stat.ModTime().Before(folder.UpdatedAt) { it.stat.ModTime().Before(folder.UpdatedAt) {
// we found the record but it hasn't changed // we found the record but it hasn't changed
s.curFolders.Push(&folder)
return nil return nil
} }
folder.LeftPath = it.directory folder.LeftPath = it.directory
folder.RightPath = it.filename folder.RightPath = it.filename
s.tx.Save(&folder) s.tx.Save(folder)
folder.IsNew = true folder.IsNew = true
s.curFolders.Push(&folder)
return nil return nil
} }
func (s *Scanner) handleTrack(it *item) error { func (s *Scanner) handleTrack(it *item) error {
// //
// set track basics // set track basics
var track model.Track track := &model.Track{}
defer func() {
// id will will be found (the first early return)
// or created the tx.Save(track)
s.seenTracks[track.ID] = struct{}{}
}()
err := s.tx. err := s.tx.
Where(model.Track{ Where(model.Track{
AlbumID: s.curFolderID(), AlbumID: s.curFolderID(),
Filename: it.filename, Filename: it.filename,
}). }).
First(&track). First(track).
Error Error
if !gorm.IsRecordNotFoundError(err) && if !gorm.IsRecordNotFoundError(err) &&
it.stat.ModTime().Before(track.UpdatedAt) { it.stat.ModTime().Before(track.UpdatedAt) {
s.seenTracks[track.ID] = struct{}{}
// we found the record but it hasn't changed // we found the record but it hasn't changed
return nil return nil
} }
@@ -125,18 +128,17 @@ func (s *Scanner) handleTrack(it *item) error {
track.TagYear = tags.Year() track.TagYear = tags.Year()
// //
// set album artist basics // set album artist basics
var artist model.Artist artist := &model.Artist{}
err = s.tx. err = s.tx.
Where("name = ?", tags.AlbumArtist()). Where("name = ?", tags.AlbumArtist()).
First(&artist). First(artist).
Error Error
if gorm.IsRecordNotFoundError(err) { if gorm.IsRecordNotFoundError(err) {
artist.Name = tags.AlbumArtist() artist.Name = tags.AlbumArtist()
s.tx.Save(&artist) s.tx.Save(artist)
} }
track.ArtistID = artist.ID track.ArtistID = artist.ID
s.tx.Save(&track) s.tx.Save(track)
s.seenTracks[track.ID] = struct{}{}
// //
// set album if this is the first track in the folder // set album if this is the first track in the folder
if !s.curFolder().IsNew { if !s.curFolder().IsNew {

View File

@@ -8,14 +8,17 @@ import (
) )
func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album { func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
return &subsonic.Album{ ret := &subsonic.Album{
ID: a.ID, ID: a.ID,
Name: a.TagTitle, Name: a.TagTitle,
Created: a.CreatedAt, Created: a.CreatedAt,
CoverID: a.ID, CoverID: a.ID,
Artist: artist.Name,
ArtistID: artist.ID,
} }
if artist != nil {
ret.Artist = artist.Name
ret.ArtistID = artist.ID
}
return ret
} }
func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track { func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track {

View File

@@ -24,10 +24,10 @@ type Controller struct {
} }
func (c *Controller) GetSetting(key string) string { func (c *Controller) GetSetting(key string) string {
var setting model.Setting setting := &model.Setting{}
c.DB. c.DB.
Where("key = ?", key). Where("key = ?", key).
First(&setting) First(setting)
return setting.Value return setting.Value
} }
@@ -39,13 +39,13 @@ func (c *Controller) SetSetting(key, value string) {
} }
func (c *Controller) GetUserFromName(name string) *model.User { func (c *Controller) GetUserFromName(name string) *model.User {
var user model.User user := &model.User{}
err := c.DB. err := c.DB.
Where("name = ?", name). Where("name = ?", name).
First(&user). First(user).
Error Error
if gorm.IsRecordNotFoundError(err) { if gorm.IsRecordNotFoundError(err) {
return nil return nil
} }
return &user return user
} }

View File

@@ -48,11 +48,11 @@ func (c *Controller) ServeLogout(w http.ResponseWriter, r *http.Request) {
} }
func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) { func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
var data templateData data := &templateData{}
c.DB.Table("artists").Count(&data.ArtistCount) c.DB.Table("artists").Count(data.ArtistCount)
c.DB.Table("albums").Count(&data.AlbumCount) c.DB.Table("albums").Count(data.AlbumCount)
c.DB.Table("tracks").Count(&data.TrackCount) c.DB.Table("tracks").Count(data.TrackCount)
c.DB.Find(&data.AllUsers) c.DB.Find(data.AllUsers)
data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key") data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key")
scheme := firstExisting( scheme := firstExisting(
"http", // fallback "http", // fallback
@@ -66,7 +66,7 @@ func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
r.Host, r.Host,
) )
data.RequestRoot = fmt.Sprintf("%s://%s", scheme, host) data.RequestRoot = fmt.Sprintf("%s://%s", scheme, host)
renderTemplate(w, r, c.Templates["home"], &data) renderTemplate(w, r, c.Templates["home"], data)
} }
func (c *Controller) ServeChangeOwnPassword(w http.ResponseWriter, r *http.Request) { func (c *Controller) ServeChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
@@ -127,27 +127,27 @@ func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request)
http.Error(w, "please provide a username", 400) http.Error(w, "please provide a username", 400)
return return
} }
var user model.User user := &model.User{}
err := c.DB. err := c.DB.
Where("name = ?", username). Where("name = ?", username).
First(&user). First(user).
Error 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
} }
var data templateData data := &templateData{}
data.SelectedUser = &user data.SelectedUser = user
renderTemplate(w, r, c.Templates["change_password"], &data) renderTemplate(w, r, c.Templates["change_password"], data)
} }
func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Request) { func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Request) {
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 user := &model.User{}
c.DB. c.DB.
Where("name = ?", username). Where("name = ?", username).
First(&user) 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)
@@ -158,7 +158,7 @@ func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Reques
return return
} }
user.Password = passwordOne user.Password = passwordOne
c.DB.Save(&user) c.DB.Save(user)
http.Redirect(w, r, "/admin/home", http.StatusSeeOther) http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
} }
@@ -168,27 +168,27 @@ func (c *Controller) ServeDeleteUser(w http.ResponseWriter, r *http.Request) {
http.Error(w, "please provide a username", 400) http.Error(w, "please provide a username", 400)
return return
} }
var user model.User user := &model.User{}
err := c.DB. err := c.DB.
Where("name = ?", username). Where("name = ?", username).
First(&user). First(user).
Error 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
} }
var data templateData data := &templateData{}
data.SelectedUser = &user data.SelectedUser = user
renderTemplate(w, r, c.Templates["delete_user"], &data) renderTemplate(w, r, c.Templates["delete_user"], data)
} }
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 user := &model.User{}
c.DB. c.DB.
Where("name = ?", username). Where("name = ?", username).
First(&user) 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)
} }
@@ -232,10 +232,10 @@ func (c *Controller) ServeCreateUserDo(w http.ResponseWriter, r *http.Request) {
} }
func (c *Controller) ServeUpdateLastFMAPIKey(w http.ResponseWriter, r *http.Request) { func (c *Controller) ServeUpdateLastFMAPIKey(w http.ResponseWriter, r *http.Request) {
var data templateData data := &templateData{}
data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key") data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key")
data.CurrentLastFMAPISecret = c.GetSetting("lastfm_secret") data.CurrentLastFMAPISecret = c.GetSetting("lastfm_secret")
renderTemplate(w, r, c.Templates["update_lastfm_api_key"], &data) renderTemplate(w, r, c.Templates["update_lastfm_api_key"], data)
} }
func (c *Controller) ServeUpdateLastFMAPIKeyDo(w http.ResponseWriter, r *http.Request) { func (c *Controller) ServeUpdateLastFMAPIKeyDo(w http.ResponseWriter, r *http.Request) {

View File

@@ -19,12 +19,12 @@ 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.Album var folders []*model.Album
c.DB. c.DB.
Where("parent_id = 1"). Where("parent_id = 1").
Find(&folders) Find(&folders)
var indexMap = make(map[rune]*subsonic.Index) indexMap := make(map[rune]*subsonic.Index)
var indexes []*subsonic.Index indexes := []*subsonic.Index{}
for _, folder := range folders { for _, folder := range folders {
i := indexOf(folder.RightPath) i := indexOf(folder.RightPath)
index, ok := indexMap[i] index, ok := indexMap[i]
@@ -37,7 +37,7 @@ func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
indexes = append(indexes, index) indexes = append(indexes, index)
} }
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
@@ -57,28 +57,28 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
return return
} }
childrenObj := []*subsonic.Track{} childrenObj := []*subsonic.Track{}
var folder model.Album 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.Album var childFolders []*model.Album
c.DB. c.DB.
Where("parent_id = ?", id). Where("parent_id = ?", id).
Find(&childFolders) Find(&childFolders)
for _, c := range childFolders { for _, c := range childFolders {
childrenObj = append(childrenObj, childrenObj = append(childrenObj,
makeChildFromFolder(&c, &folder)) makeChildFromFolder(c, folder))
} }
// //
// 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("album_id = ?", id). Where("album_id = ?", id).
Preload("Album"). Preload("Album").
Order("filename"). Order("filename").
Find(&childTracks) Find(&childTracks)
for _, c := range childTracks { for _, c := range childTracks {
toAppend := makeChildFromTrack(&c, &folder) 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
toAppend.ContentType = "audio/mpeg" toAppend.ContentType = "audio/mpeg"
@@ -89,7 +89,7 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
// //
// respond section // respond section
sub := subsonic.NewResponse() sub := subsonic.NewResponse()
sub.Directory = makeDirFromFolder(&folder, childrenObj) sub.Directory = makeDirFromFolder(folder, childrenObj)
respond(w, r, sub) respond(w, r, sub)
} }
@@ -133,7 +133,7 @@ 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.Album var folders []*model.Album
q. q.
Where("albums.tag_artist_id IS NOT NULL"). Where("albums.tag_artist_id IS NOT NULL").
Offset(getIntParamOr(r, "offset", 0)). Offset(getIntParamOr(r, "offset", 0)).
@@ -144,7 +144,7 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
sub.Albums = &subsonic.Albums{} sub.Albums = &subsonic.Albums{}
for _, folder := range folders { for _, folder := range folders {
sub.Albums.List = append(sub.Albums.List, sub.Albums.List = append(sub.Albums.List,
makeAlbumFromFolder(&folder)) makeAlbumFromFolder(folder))
} }
respond(w, r, sub) respond(w, r, sub)
} }
@@ -160,7 +160,7 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
results := &subsonic.SearchResultTwo{} results := &subsonic.SearchResultTwo{}
// //
// search "artists" // search "artists"
var artists []model.Album var artists []*model.Album
c.DB. c.DB.
Where("parent_id = 1 AND right_path LIKE ?", query). Where("parent_id = 1 AND right_path LIKE ?", query).
Offset(getIntParamOr(r, "artistOffset", 0)). Offset(getIntParamOr(r, "artistOffset", 0)).
@@ -168,11 +168,11 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
Find(&artists) Find(&artists)
for _, a := range artists { for _, a := range artists {
results.Artists = append(results.Artists, results.Artists = append(results.Artists,
makeDirFromFolder(&a, nil)) makeDirFromFolder(a, nil))
} }
// //
// search "albums" // search "albums"
var albums []model.Album var albums []*model.Album
c.DB. c.DB.
Preload("Parent"). Preload("Parent").
Where("tag_artist_id IS NOT NULL AND right_path LIKE ?", query). Where("tag_artist_id IS NOT NULL AND right_path LIKE ?", query).
@@ -181,11 +181,11 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
Find(&albums) Find(&albums)
for _, a := range albums { for _, a := range albums {
results.Albums = append(results.Albums, results.Albums = append(results.Albums,
makeChildFromFolder(&a, a.Parent)) makeChildFromFolder(a, a.Parent))
} }
// //
// search tracks // search tracks
var tracks []model.Track var tracks []*model.Track
c.DB. c.DB.
Preload("Album"). Preload("Album").
Where("filename LIKE ?", query). Where("filename LIKE ?", query).
@@ -194,7 +194,7 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
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.Album)) makeChildFromTrack(t, t.Album))
} }
// //
sub := subsonic.NewResponse() sub := subsonic.NewResponse()

View File

@@ -13,10 +13,10 @@ import (
) )
func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) { func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
var artists []model.Artist var artists []*model.Artist
c.DB.Find(&artists) c.DB.Find(&artists)
var indexMap = make(map[rune]*subsonic.Index) indexMap := make(map[rune]*subsonic.Index)
var indexes subsonic.Artists indexes := &subsonic.Artists{}
for _, artist := range artists { for _, artist := range artists {
i := indexOf(artist.Name) i := indexOf(artist.Name)
index, ok := indexMap[i] index, ok := indexMap[i]
@@ -29,13 +29,13 @@ func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
indexes.List = append(indexes.List, index) indexes.List = append(indexes.List, index)
} }
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()
sub.Artists = &indexes sub.Artists = indexes
respond(w, r, sub) respond(w, r, sub)
} }
@@ -45,15 +45,15 @@ func (c *Controller) GetArtist(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 artist model.Artist artist := &model.Artist{}
c.DB. c.DB.
Preload("Albums"). Preload("Albums").
First(&artist, id) First(artist, id)
sub := subsonic.NewResponse() sub := subsonic.NewResponse()
sub.Artist = makeArtistFromArtist(&artist) sub.Artist = makeArtistFromArtist(artist)
for _, album := range artist.Albums { for _, album := range artist.Albums {
sub.Artist.Albums = append(sub.Artist.Albums, sub.Artist.Albums = append(sub.Artist.Albums,
makeAlbumFromAlbum(&album, &artist)) makeAlbumFromAlbum(album, artist))
} }
respond(w, r, sub) respond(w, r, sub)
} }
@@ -64,23 +64,23 @@ func (c *Controller) GetAlbum(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 album model.Album album := &model.Album{}
err = c.DB. err = c.DB.
Preload("TagArtist"). Preload("TagArtist").
Preload("Tracks", func(db *gorm.DB) *gorm.DB { Preload("Tracks", func(db *gorm.DB) *gorm.DB {
return db.Order("tracks.tag_track_number") return db.Order("tracks.tag_track_number")
}). }).
First(&album, id). First(album, id).
Error Error
if gorm.IsRecordNotFoundError(err) { if gorm.IsRecordNotFoundError(err) {
respondError(w, r, 10, "couldn't find an album with that id") respondError(w, r, 10, "couldn't find an album with that id")
return return
} }
sub := subsonic.NewResponse() sub := subsonic.NewResponse()
sub.Album = makeAlbumFromAlbum(&album, &album.TagArtist) 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))
} }
respond(w, r, sub) respond(w, r, sub)
} }
@@ -131,7 +131,7 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
"unknown value `%s` for parameter 'type'", listType) "unknown value `%s` for parameter 'type'", listType)
return return
} }
var albums []model.Album var albums []*model.Album
q. q.
Where("albums.tag_artist_id IS NOT NULL"). Where("albums.tag_artist_id IS NOT NULL").
Offset(getIntParamOr(r, "offset", 0)). Offset(getIntParamOr(r, "offset", 0)).
@@ -142,7 +142,7 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
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.TagArtist)) makeAlbumFromAlbum(album, album.TagArtist))
} }
respond(w, r, sub) respond(w, r, sub)
} }
@@ -158,7 +158,7 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
results := &subsonic.SearchResultThree{} results := &subsonic.SearchResultThree{}
// //
// search "artists" // search "artists"
var artists []model.Artist var artists []*model.Artist
c.DB. c.DB.
Where("name LIKE ?", query). Where("name LIKE ?", query).
Offset(getIntParamOr(r, "artistOffset", 0)). Offset(getIntParamOr(r, "artistOffset", 0)).
@@ -166,11 +166,11 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
Find(&artists) Find(&artists)
for _, a := range artists { for _, a := range artists {
results.Artists = append(results.Artists, results.Artists = append(results.Artists,
makeArtistFromArtist(&a)) makeArtistFromArtist(a))
} }
// //
// search "albums" // search "albums"
var albums []model.Album var albums []*model.Album
c.DB. c.DB.
Preload("TagArtist"). Preload("TagArtist").
Where("tag_title LIKE ?", query). Where("tag_title LIKE ?", query).
@@ -179,11 +179,11 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
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.TagArtist)) 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("tag_title LIKE ?", query). Where("tag_title LIKE ?", query).
@@ -192,7 +192,7 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
Find(&tracks) Find(&tracks)
for _, t := range tracks { for _, t := range tracks {
results.Tracks = append(results.Tracks, results.Tracks = append(results.Tracks,
makeTrackFromTrack(&t, &t.Album)) makeTrackFromTrack(t, t.Album))
} }
sub := subsonic.NewResponse() sub := subsonic.NewResponse()
sub.SearchResultThree = results sub.SearchResultThree = results

View File

@@ -1,6 +1,7 @@
package handler package handler
import ( import (
"log"
"net/http" "net/http"
"os" "os"
"path" "path"
@@ -32,10 +33,10 @@ func (c *Controller) Stream(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 track model.Track track := &model.Track{}
err = c.DB. err = c.DB.
Preload("Album"). Preload("Album").
First(&track, id). First(track, id).
Error Error
if gorm.IsRecordNotFoundError(err) { 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)
@@ -75,10 +76,10 @@ 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 folder model.Album folder := &model.Album{}
err = c.DB. err = c.DB.
Select("id, path, cover"). Select("id, path, cover").
First(&folder, id). First(folder, id).
Error Error
if gorm.IsRecordNotFoundError(err) { if gorm.IsRecordNotFoundError(err) {
respondError(w, r, 10, "could not find a cover with that id") respondError(w, r, 10, "could not find a cover with that id")
@@ -123,17 +124,17 @@ func (c *Controller) Scrobble(w http.ResponseWriter, r *http.Request) {
return return
} }
// fetch track for getting info to send to last.fm function // fetch track for getting info to send to last.fm function
var track model.Track track := &model.Track{}
c.DB. c.DB.
Preload("Album"). Preload("Album").
Preload("Artist"). Preload("Artist").
First(&track, id) First(track, id)
// scrobble with above info // scrobble with above info
err = lastfm.Scrobble( err = lastfm.Scrobble(
c.GetSetting("lastfm_api_key"), c.GetSetting("lastfm_api_key"),
c.GetSetting("lastfm_secret"), c.GetSetting("lastfm_secret"),
user.LastFMSession, user.LastFMSession,
&track, track,
// clients will provide time in miliseconds, so use that or // clients will provide time in miliseconds, so use that or
// instead convert UnixNano to miliseconds // instead convert UnixNano to miliseconds
getIntParamOr(r, "time", int(time.Now().UnixNano()/1e6)), getIntParamOr(r, "time", int(time.Now().UnixNano()/1e6)),
@@ -158,14 +159,22 @@ func (c *Controller) GetMusicFolders(w http.ResponseWriter, r *http.Request) {
} }
func (c *Controller) StartScan(w http.ResponseWriter, r *http.Request) { func (c *Controller) StartScan(w http.ResponseWriter, r *http.Request) {
scanC := scanner.New(c.DB, c.MusicPath) go func() {
go scanC.Start() err := scanner.
New(c.DB, c.MusicPath).
Start()
if err != nil {
log.Printf("error while scanning: %v\n", err)
}
}()
c.GetScanStatus(w, r) c.GetScanStatus(w, r)
} }
func (c *Controller) GetScanStatus(w http.ResponseWriter, r *http.Request) { func (c *Controller) GetScanStatus(w http.ResponseWriter, r *http.Request) {
var trackCount int var trackCount int
c.DB.Model(&model.Track{}).Count(&trackCount) c.DB.
Model(model.Track{}).
Count(&trackCount)
sub := subsonic.NewResponse() sub := subsonic.NewResponse()
sub.ScanStatus = &subsonic.ScanStatus{ sub.ScanStatus = &subsonic.ScanStatus{
Scanning: atomic.LoadInt32(&scanner.IsScanning) == 1, Scanning: atomic.LoadInt32(&scanner.IsScanning) == 1,

View File

@@ -44,7 +44,9 @@ func testNameToPath(name string) string {
func testQueryCases(t *testing.T, handler http.HandlerFunc, cases []*queryCase) { func testQueryCases(t *testing.T, handler http.HandlerFunc, cases []*queryCase) {
for _, qc := range cases { for _, qc := range cases {
qc := qc // pin
t.Run(qc.expectPath, func(t *testing.T) { t.Run(qc.expectPath, func(t *testing.T) {
t.Parallel()
// ensure the handlers give us json // ensure the handlers give us json
qc.params.Add("f", "json") qc.params.Add("f", "json")
req, _ := http.NewRequest("", "?"+qc.params.Encode(), nil) req, _ := http.NewRequest("", "?"+qc.params.Encode(), nil)

View File

@@ -84,13 +84,13 @@ func makeRequest(method string, params url.Values) (*LastFM, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
decoder := xml.NewDecoder(resp.Body) decoder := xml.NewDecoder(resp.Body)
var lastfm LastFM lastfm := &LastFM{}
err = decoder.Decode(&lastfm) err = decoder.Decode(lastfm)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "decoding") return nil, errors.Wrap(err, "decoding")
} }
if lastfm.Error != nil { if lastfm.Error != nil {
return nil, fmt.Errorf("parsing: %v", lastfm.Error.Value) return nil, fmt.Errorf("parsing: %v", lastfm.Error.Value)
} }
return &lastfm, nil return lastfm, nil
} }