use references when possible
This commit is contained in:
@@ -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/
|
||||||
|
|||||||
@@ -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:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user