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:
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- text: "weak cryptographic primitive"
linters:
- gosec
- path: model/model.go
- path: model/model\.go
linters:
- lll
- path: server/handler/

View File

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

View File

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

View File

@@ -8,14 +8,17 @@ import (
)
func makeAlbumFromAlbum(a *model.Album, artist *model.Artist) *subsonic.Album {
return &subsonic.Album{
ret := &subsonic.Album{
ID: a.ID,
Name: a.TagTitle,
Created: a.CreatedAt,
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 {

View File

@@ -24,10 +24,10 @@ type Controller struct {
}
func (c *Controller) GetSetting(key string) string {
var setting model.Setting
setting := &model.Setting{}
c.DB.
Where("key = ?", key).
First(&setting)
First(setting)
return setting.Value
}
@@ -39,13 +39,13 @@ func (c *Controller) SetSetting(key, value string) {
}
func (c *Controller) GetUserFromName(name string) *model.User {
var user model.User
user := &model.User{}
err := c.DB.
Where("name = ?", name).
First(&user).
First(user).
Error
if gorm.IsRecordNotFoundError(err) {
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) {
var data templateData
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)
data := &templateData{}
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)
data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key")
scheme := firstExisting(
"http", // fallback
@@ -66,7 +66,7 @@ func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
r.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) {
@@ -127,27 +127,27 @@ func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request)
http.Error(w, "please provide a username", 400)
return
}
var user model.User
user := &model.User{}
err := c.DB.
Where("name = ?", username).
First(&user).
First(user).
Error
if gorm.IsRecordNotFoundError(err) {
http.Error(w, "couldn't find a user with that name", 400)
return
}
var data templateData
data.SelectedUser = &user
renderTemplate(w, r, c.Templates["change_password"], &data)
data := &templateData{}
data.SelectedUser = user
renderTemplate(w, r, c.Templates["change_password"], data)
}
func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value(contextSessionKey).(*sessions.Session)
username := r.URL.Query().Get("user")
var user model.User
user := &model.User{}
c.DB.
Where("name = ?", username).
First(&user)
First(user)
passwordOne := r.FormValue("password_one")
passwordTwo := r.FormValue("password_two")
err := validatePasswords(passwordOne, passwordTwo)
@@ -158,7 +158,7 @@ func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Reques
return
}
user.Password = passwordOne
c.DB.Save(&user)
c.DB.Save(user)
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)
return
}
var user model.User
user := &model.User{}
err := c.DB.
Where("name = ?", username).
First(&user).
First(user).
Error
if gorm.IsRecordNotFoundError(err) {
http.Error(w, "couldn't find a user with that name", 400)
return
}
var data templateData
data.SelectedUser = &user
renderTemplate(w, r, c.Templates["delete_user"], &data)
data := &templateData{}
data.SelectedUser = user
renderTemplate(w, r, c.Templates["delete_user"], data)
}
func (c *Controller) ServeDeleteUserDo(w http.ResponseWriter, r *http.Request) {
username := r.URL.Query().Get("user")
var user model.User
user := &model.User{}
c.DB.
Where("name = ?", username).
First(&user)
c.DB.Delete(&user)
First(user)
c.DB.Delete(user)
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) {
var data templateData
data := &templateData{}
data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key")
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) {

View File

@@ -19,12 +19,12 @@ import (
// under the root directory
func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
var folders []model.Album
var folders []*model.Album
c.DB.
Where("parent_id = 1").
Find(&folders)
var indexMap = make(map[rune]*subsonic.Index)
var indexes []*subsonic.Index
indexMap := make(map[rune]*subsonic.Index)
indexes := []*subsonic.Index{}
for _, folder := range folders {
i := indexOf(folder.RightPath)
index, ok := indexMap[i]
@@ -37,7 +37,7 @@ func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
indexes = append(indexes, index)
}
index.Artists = append(index.Artists,
makeArtistFromFolder(&folder))
makeArtistFromFolder(folder))
}
sort.Slice(indexes, func(i, j int) bool {
return indexes[i].Name < indexes[j].Name
@@ -57,28 +57,28 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
return
}
childrenObj := []*subsonic.Track{}
var folder model.Album
c.DB.First(&folder, id)
folder := &model.Album{}
c.DB.First(folder, id)
//
// start looking for child childFolders in the current dir
var childFolders []model.Album
var childFolders []*model.Album
c.DB.
Where("parent_id = ?", id).
Find(&childFolders)
for _, c := range childFolders {
childrenObj = append(childrenObj,
makeChildFromFolder(&c, &folder))
makeChildFromFolder(c, folder))
}
//
// start looking for child childTracks in the current dir
var childTracks []model.Track
var childTracks []*model.Track
c.DB.
Where("album_id = ?", id).
Preload("Album").
Order("filename").
Find(&childTracks)
for _, c := range childTracks {
toAppend := makeChildFromTrack(&c, &folder)
toAppend := makeChildFromTrack(c, folder)
if getStrParam(r, "c") == "Jamstash" {
// jamstash thinks it can't play flacs
toAppend.ContentType = "audio/mpeg"
@@ -89,7 +89,7 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) {
//
// respond section
sub := subsonic.NewResponse()
sub.Directory = makeDirFromFolder(&folder, childrenObj)
sub.Directory = makeDirFromFolder(folder, childrenObj)
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)
return
}
var folders []model.Album
var folders []*model.Album
q.
Where("albums.tag_artist_id IS NOT NULL").
Offset(getIntParamOr(r, "offset", 0)).
@@ -144,7 +144,7 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) {
sub.Albums = &subsonic.Albums{}
for _, folder := range folders {
sub.Albums.List = append(sub.Albums.List,
makeAlbumFromFolder(&folder))
makeAlbumFromFolder(folder))
}
respond(w, r, sub)
}
@@ -160,7 +160,7 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
results := &subsonic.SearchResultTwo{}
//
// search "artists"
var artists []model.Album
var artists []*model.Album
c.DB.
Where("parent_id = 1 AND right_path LIKE ?", query).
Offset(getIntParamOr(r, "artistOffset", 0)).
@@ -168,11 +168,11 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
Find(&artists)
for _, a := range artists {
results.Artists = append(results.Artists,
makeDirFromFolder(&a, nil))
makeDirFromFolder(a, nil))
}
//
// search "albums"
var albums []model.Album
var albums []*model.Album
c.DB.
Preload("Parent").
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)
for _, a := range albums {
results.Albums = append(results.Albums,
makeChildFromFolder(&a, a.Parent))
makeChildFromFolder(a, a.Parent))
}
//
// search tracks
var tracks []model.Track
var tracks []*model.Track
c.DB.
Preload("Album").
Where("filename LIKE ?", query).
@@ -194,7 +194,7 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) {
Find(&tracks)
for _, t := range tracks {
results.Tracks = append(results.Tracks,
makeChildFromTrack(&t, &t.Album))
makeChildFromTrack(t, t.Album))
}
//
sub := subsonic.NewResponse()

View File

@@ -13,10 +13,10 @@ import (
)
func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
var artists []model.Artist
var artists []*model.Artist
c.DB.Find(&artists)
var indexMap = make(map[rune]*subsonic.Index)
var indexes subsonic.Artists
indexMap := make(map[rune]*subsonic.Index)
indexes := &subsonic.Artists{}
for _, artist := range artists {
i := indexOf(artist.Name)
index, ok := indexMap[i]
@@ -29,13 +29,13 @@ func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) {
indexes.List = append(indexes.List, index)
}
index.Artists = append(index.Artists,
makeArtistFromArtist(&artist))
makeArtistFromArtist(artist))
}
sort.Slice(indexes.List, func(i, j int) bool {
return indexes.List[i].Name < indexes.List[j].Name
})
sub := subsonic.NewResponse()
sub.Artists = &indexes
sub.Artists = indexes
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")
return
}
var artist model.Artist
artist := &model.Artist{}
c.DB.
Preload("Albums").
First(&artist, id)
First(artist, id)
sub := subsonic.NewResponse()
sub.Artist = makeArtistFromArtist(&artist)
sub.Artist = makeArtistFromArtist(artist)
for _, album := range artist.Albums {
sub.Artist.Albums = append(sub.Artist.Albums,
makeAlbumFromAlbum(&album, &artist))
makeAlbumFromAlbum(album, artist))
}
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")
return
}
var album model.Album
album := &model.Album{}
err = c.DB.
Preload("TagArtist").
Preload("Tracks", func(db *gorm.DB) *gorm.DB {
return db.Order("tracks.tag_track_number")
}).
First(&album, id).
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 = makeAlbumFromAlbum(&album, &album.TagArtist)
sub.Album = makeAlbumFromAlbum(album, album.TagArtist)
for _, track := range album.Tracks {
sub.Album.Tracks = append(sub.Album.Tracks,
makeTrackFromTrack(&track, &album))
makeTrackFromTrack(track, album))
}
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)
return
}
var albums []model.Album
var albums []*model.Album
q.
Where("albums.tag_artist_id IS NOT NULL").
Offset(getIntParamOr(r, "offset", 0)).
@@ -142,7 +142,7 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) {
sub.AlbumsTwo = &subsonic.Albums{}
for _, album := range albums {
sub.AlbumsTwo.List = append(sub.AlbumsTwo.List,
makeAlbumFromAlbum(&album, &album.TagArtist))
makeAlbumFromAlbum(album, album.TagArtist))
}
respond(w, r, sub)
}
@@ -158,7 +158,7 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
results := &subsonic.SearchResultThree{}
//
// search "artists"
var artists []model.Artist
var artists []*model.Artist
c.DB.
Where("name LIKE ?", query).
Offset(getIntParamOr(r, "artistOffset", 0)).
@@ -166,11 +166,11 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
Find(&artists)
for _, a := range artists {
results.Artists = append(results.Artists,
makeArtistFromArtist(&a))
makeArtistFromArtist(a))
}
//
// search "albums"
var albums []model.Album
var albums []*model.Album
c.DB.
Preload("TagArtist").
Where("tag_title LIKE ?", query).
@@ -179,11 +179,11 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
Find(&albums)
for _, a := range albums {
results.Albums = append(results.Albums,
makeAlbumFromAlbum(&a, &a.TagArtist))
makeAlbumFromAlbum(a, a.TagArtist))
}
//
// search tracks
var tracks []model.Track
var tracks []*model.Track
c.DB.
Preload("Album").
Where("tag_title LIKE ?", query).
@@ -192,7 +192,7 @@ func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) {
Find(&tracks)
for _, t := range tracks {
results.Tracks = append(results.Tracks,
makeTrackFromTrack(&t, &t.Album))
makeTrackFromTrack(t, t.Album))
}
sub := subsonic.NewResponse()
sub.SearchResultThree = results

View File

@@ -1,6 +1,7 @@
package handler
import (
"log"
"net/http"
"os"
"path"
@@ -32,10 +33,10 @@ func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) {
respondError(w, r, 10, "please provide an `id` parameter")
return
}
var track model.Track
track := &model.Track{}
err = c.DB.
Preload("Album").
First(&track, id).
First(track, id).
Error
if gorm.IsRecordNotFoundError(err) {
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")
return
}
var folder model.Album
folder := &model.Album{}
err = c.DB.
Select("id, path, cover").
First(&folder, id).
First(folder, id).
Error
if gorm.IsRecordNotFoundError(err) {
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
}
// fetch track for getting info to send to last.fm function
var track model.Track
track := &model.Track{}
c.DB.
Preload("Album").
Preload("Artist").
First(&track, id)
First(track, id)
// scrobble with above info
err = lastfm.Scrobble(
c.GetSetting("lastfm_api_key"),
c.GetSetting("lastfm_secret"),
user.LastFMSession,
&track,
track,
// clients will provide time in miliseconds, so use that or
// instead convert UnixNano to miliseconds
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) {
scanC := scanner.New(c.DB, c.MusicPath)
go scanC.Start()
go func() {
err := scanner.
New(c.DB, c.MusicPath).
Start()
if err != nil {
log.Printf("error while scanning: %v\n", err)
}
}()
c.GetScanStatus(w, r)
}
func (c *Controller) GetScanStatus(w http.ResponseWriter, r *http.Request) {
var trackCount int
c.DB.Model(&model.Track{}).Count(&trackCount)
c.DB.
Model(model.Track{}).
Count(&trackCount)
sub := subsonic.NewResponse()
sub.ScanStatus = &subsonic.ScanStatus{
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) {
for _, qc := range cases {
qc := qc // pin
t.Run(qc.expectPath, func(t *testing.T) {
t.Parallel()
// ensure the handlers give us json
qc.params.Add("f", "json")
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()
decoder := xml.NewDecoder(resp.Body)
var lastfm LastFM
err = decoder.Decode(&lastfm)
lastfm := &LastFM{}
err = decoder.Decode(lastfm)
if err != nil {
return nil, errors.Wrap(err, "decoding")
}
if lastfm.Error != nil {
return nil, fmt.Errorf("parsing: %v", lastfm.Error.Value)
}
return &lastfm, nil
return lastfm, nil
}