Merge branch 'develop'

This commit is contained in:
sentriz
2020-03-12 14:28:26 +00:00
11 changed files with 138 additions and 26 deletions

View File

@@ -41,6 +41,7 @@ func New(path string) (*DB, error) {
&migrationCreateInitUser,
&migrationMergePlaylist,
&migrationCreateTranscode,
&migrationAddGenre,
})
if err = migr.Migrate(); err != nil {
return nil, errors.Wrap(err, "migrating to latest version")

View File

@@ -79,3 +79,15 @@ var migrationCreateTranscode = gormigrate.Migration{
Error
},
}
var migrationAddGenre = gormigrate.Migration{
ID: "202003121330",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Genre{},
Album{},
Track{},
).
Error
},
}

View File

@@ -49,6 +49,15 @@ func (a *Artist) IndexName() string {
return a.Name
}
type Genre struct {
ID int `gorm:"primary_ket"`
Name string `gorm:"not null; unique_index"`
Albums []*Album `gorm:"foreignkey:TagGenreID"`
AlbumCount int `sql:"-"`
Tracks []*Track `gorm:"foreignkey:TagGenreID"`
TrackCount int `sql:"-"`
}
type Track struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
@@ -67,6 +76,8 @@ type Track struct {
TagTrackArtist string `sql:"default: null"`
TagTrackNumber int `sql:"default: null"`
TagDiscNumber int `sql:"default: null"`
TagGenre *Genre
TagGenreID int `sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
TagBrainzID string `sql:"default: null"`
}
@@ -129,7 +140,9 @@ type Album struct {
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Cover string `sql:"default: null"`
TagArtist *Artist
TagArtistID int `sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
TagArtistID int `sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
TagGenre *Genre
TagGenreID int `sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
TagTitle string `sql:"default: null"`
TagTitleUDec string `sql:"default: null"`
TagBrainzID string `sql:"default: null"`

View File

@@ -362,6 +362,27 @@ func (s *Scanner) handleTrack(it *item) error {
s.trTx.Save(artist)
}
track.ArtistID = artist.ID
//
// set genre
genreName := func() string {
if r := trTags.Genre(); r != "" {
return r
}
return "Unknown Genre"
}()
genre := &db.Genre{}
err = s.trTx.
Select("id").
Where("name=?", genreName).
First(genre).
Error
if gorm.IsRecordNotFoundError(err) {
genre.Name = genreName
s.trTx.Save(genre)
}
track.TagGenreID = genre.ID
//
// save the track
s.trTx.Save(track)
s.seenTracks[track.ID] = struct{}{}
s.seenTracksNew++
@@ -377,6 +398,7 @@ func (s *Scanner) handleTrack(it *item) error {
folder.TagBrainzID = trTags.AlbumBrainzID()
folder.TagYear = trTags.Year()
folder.TagArtistID = artist.ID
folder.TagGenreID = genre.ID
folder.ReceivedTags = true
return nil
}

View File

@@ -39,6 +39,7 @@ func (t *Tags) Artist() string { return t.firstTag("artist") }
func (t *Tags) Album() string { return t.firstTag("album") }
func (t *Tags) AlbumArtist() string { return t.firstTag("albumartist", "album artist") }
func (t *Tags) AlbumBrainzID() string { return t.firstTag("musicbrainz_albumid") }
func (t *Tags) Genre() string { return t.firstTag("genre") }
func (t *Tags) Year() int { return intSep(t.firstTag("date", "year"), "-") } // eg. 2019-6-11
func (t *Tags) TrackNumber() int { return intSep(t.firstTag("tracknumber"), "/") } // eg. 5/12
func (t *Tags) DiscNumber() int { return intSep(t.firstTag("discnumber"), "/") } // eg. 1/2

View File

@@ -115,6 +115,9 @@ func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response {
params.GetIntOr("fromYear", 1800),
params.GetIntOr("toYear", 2200))
q = q.Order("tag_year")
case "byGenre":
q = q.Joins("JOIN genres ON albums.tag_genre_id=genres.id AND genres.name=?",
params.GetOr("genre", "Unknown Genre"))
case "frequent":
user := r.Context().Value(CtxUser).(*db.User)
q = q.Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?",
@@ -275,3 +278,51 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
}
return sub
}
func (c *Controller) ServeGetGenres(r *http.Request) *spec.Response {
var genres []*db.Genre
c.DB.
Select(`*,
(SELECT count(id) FROM albums WHERE tag_genre_id=genres.id) album_count,
(SELECT count(id) FROM tracks WHERE tag_genre_id=genres.id) track_count`).
Group("genres.id").
Find(&genres)
sub := spec.NewResponse()
sub.Genres = &spec.Genres{
List: make([]*spec.Genre, len(genres)),
}
for i, genre := range genres {
sub.Genres.List[i] = spec.NewGenre(genre)
}
return sub
}
func (c *Controller) ServeGetSongsByGenre(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
genre := params.Get("genre")
if genre == "" {
return spec.NewError(10, "please provide an `genre` parameter")
}
// TODO: add musicFolderId parameter:
// (Since 1.12.0) Only return albums in the music folder with the given ID.
var tracks []*db.Track
c.DB.
Joins("JOIN albums ON tracks.album_id=albums.id").
Joins("JOIN genres ON tracks.tag_genre_id=genres.id AND genres.name=?", genre).
Preload("Album").
Offset(params.GetIntOr("offset", 0)).
Limit(params.GetIntOr("count", 10)).
Find(&tracks)
sub := spec.NewResponse()
sub.TracksByGenre = &spec.TracksByGenre{
List: make([]*spec.TrackChild, len(tracks)),
}
for i, track := range tracks {
sub.TracksByGenre.List[i] = spec.NewTrackByTags(track, track.Album)
}
return sub
}

View File

@@ -286,18 +286,28 @@ func (c *Controller) ServeGetSong(r *http.Request) *spec.Response {
func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
// TODO: add genre restraint here
var tracks []*db.Track
c.DB.DB.
Limit(params.GetIntOr("size", 10)).
Where(
"albums.tag_year BETWEEN ? AND ?",
params.GetIntOr("fromYear", 1800),
params.GetIntOr("toYear", 2200)).
q := c.DB.DB.
Joins("JOIN albums ON tracks.album_id=albums.id").
Limit(params.GetIntOr("size", 10)).
Preload("Album").
Order(gorm.Expr("random()")).
Find(&tracks)
Order(gorm.Expr("random()"))
if year, err := params.GetInt("fromYear"); err == nil {
q = q.Where("albums.tag_year >= ?", year)
}
if year, err := params.GetInt("toYear"); err == nil {
q = q.Where("albums.tag_year <= ?", year)
}
if genre := params.Get("genre"); genre != "" {
q = q.Joins(
"JOIN genres ON tracks.tag_genre_id=genres.id AND genres.name=?",
genre,
)
}
q.Find(&tracks)
sub := spec.NewResponse()
sub.RandomTracks = &spec.RandomTracks{}
sub.RandomTracks.List = make([]*spec.TrackChild, len(tracks))

View File

@@ -1,17 +1,4 @@
package ctrlsubsonic
import (
"net/http"
"senan.xyz/g/gonic/server/ctrlsubsonic/spec"
)
// NOTE: when these are implemented, they should be moved to their
// respective _by_folder or _by_tag file
func (c *Controller) ServeGetGenres(r *http.Request) *spec.Response {
sub := spec.NewResponse()
sub.Genres = &spec.Genres{}
sub.Genres.List = []*spec.Genre{}
return sub
}

View File

@@ -72,3 +72,11 @@ func NewArtistByTags(a *db.Artist) *Artist {
AlbumCount: a.AlbumCount,
}
}
func NewGenre(g *db.Genre) *Genre {
return &Genre{
Name: g.Name,
AlbumCount: g.AlbumCount,
SongCount: g.TrackCount,
}
}

View File

@@ -28,6 +28,7 @@ type Response struct {
Artist *Artist `xml:"artist" json:"artist,omitempty"`
Directory *Directory `xml:"directory" json:"directory,omitempty"`
RandomTracks *RandomTracks `xml:"randomSongs" json:"randomSongs,omitempty"`
TracksByGenre *TracksByGenre `xml:"songsByGenre" json:"songsByGenre,omitempty"`
MusicFolders *MusicFolders `xml:"musicFolders" json:"musicFolders,omitempty"`
ScanStatus *ScanStatus `xml:"scanStatus" json:"scanStatus,omitempty"`
Licence *Licence `xml:"license" json:"license,omitempty"`
@@ -109,6 +110,10 @@ type RandomTracks struct {
List []*TrackChild `xml:"song" json:"song"`
}
type TracksByGenre struct {
List []*TrackChild `xml:"song" json:"song"`
}
type TrackChild struct {
Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID int `xml:"albumId,attr,omitempty" json:"albumId,omitempty,string"`
@@ -250,8 +255,9 @@ type Genres struct {
}
type Genre struct {
SongCount string `xml:"songCount,attr"`
AlbumCount string `xml:"albumCount,attr"`
Name string `xml:",chardata",json:"value"`
SongCount int `xml:"songCount,attr,omitempty" json:"songCount,omitempty"`
AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"`
}
type PlayQueue struct {

View File

@@ -150,6 +150,7 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
r.Handle("/getPlayQueue{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetPlayQueue))
r.Handle("/getSong{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetSong))
r.Handle("/getRandomSongs{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetRandomSongs))
r.Handle("/getSongsByGenre{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetSongsByGenre))
// ** begin raw
r.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeDownload))
r.Handle("/getCoverArt{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeGetCoverArt))
@@ -166,8 +167,8 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
r.Handle("/getMusicDirectory{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetMusicDirectory))
r.Handle("/getAlbumList{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetAlbumList))
r.Handle("/search2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeSearchTwo))
// ** begin unimplemented
r.Handle("/getGenres{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetGenres))
// ** begin unimplemented
// middlewares should be run for not found handler
// https://github.com/gorilla/mux/issues/416
notFoundHandler := ctrl.H(ctrl.ServeNotFound)