diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 8a98736..24f9b9e 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -61,7 +61,7 @@ func readTags(fullPath string) (tag.Metadata, error) { return tags, nil } -func updateAlbum(fullPath string, albumArtistID int, title string) { +func updateAlbum(fullPath string, album *db.Album) { if currentAlbum.ID != 0 { return } @@ -76,8 +76,9 @@ func updateAlbum(fullPath string, albumArtistID int, title string) { } currentAlbum = &db.Album{ Path: directory, - AlbumArtistID: albumArtistID, - Title: title, + Title: album.Title, + AlbumArtistID: album.AlbumArtistID, + Year: album.Year, } tx.Save(currentAlbum) } @@ -182,7 +183,11 @@ func handleTrack(fullPath string, stat os.FileInfo, mime, exten string) error { // // set temporary album's basics - will be updated with // cover after the tracks inserted when we exit the folder - updateAlbum(fullPath, albumArtist.ID, tags.Album()) + updateAlbum(fullPath, &db.Album{ + AlbumArtistID: albumArtist.ID, + Title: tags.Album(), + Year: tags.Year(), + }) // // update the track with our new album and finally save track.AlbumID = currentAlbum.ID diff --git a/cmd/server/main.go b/cmd/server/main.go index 1bade1b..c495ee7 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -70,8 +70,8 @@ func setSubsonicRoutes(cont handler.Controller, mux *http.ServeMux) { // browse by tag mux.HandleFunc("/rest/getAlbum", withWare(cont.GetAlbum)) mux.HandleFunc("/rest/getAlbum.view", withWare(cont.GetAlbum)) - mux.HandleFunc("/rest/getAlbumList2", withWare(cont.GetAlbumList)) - mux.HandleFunc("/rest/getAlbumList2.view", withWare(cont.GetAlbumList)) + mux.HandleFunc("/rest/getAlbumList2", withWare(cont.GetAlbumListTwo)) + mux.HandleFunc("/rest/getAlbumList2.view", withWare(cont.GetAlbumListTwo)) mux.HandleFunc("/rest/getArtist", withWare(cont.GetArtist)) mux.HandleFunc("/rest/getArtist.view", withWare(cont.GetArtist)) mux.HandleFunc("/rest/getArtists", withWare(cont.GetArtists)) diff --git a/db/model.go b/db/model.go index 30d83fc..917c456 100644 --- a/db/model.go +++ b/db/model.go @@ -16,6 +16,7 @@ type Album struct { Path string `gorm:"not null;unique_index"` CoverID int Cover Cover + Year int Tracks []Track } @@ -82,10 +83,11 @@ type Setting struct { type Play struct { IDBase User User - UserID int - Track Track - TrackID int + UserID int `gorm:"not null;index"` + Album Album + AlbumID int `gorm:"not null;index"` Time time.Time + Count int } // Folder represents the settings table diff --git a/handler/handler_admin_const.go b/handler/handler_const.go similarity index 100% rename from handler/handler_admin_const.go rename to handler/handler_const.go diff --git a/handler/handler_sub_by_tags.go b/handler/handler_sub_by_tags.go index 6f82a5c..36607cb 100644 --- a/handler/handler_sub_by_tags.go +++ b/handler/handler_sub_by_tags.go @@ -10,12 +10,6 @@ import ( "github.com/sentriz/gonic/subsonic" ) -var orderExpr = map[string]interface{}{ - "random": gorm.Expr("random()"), - "newest": "updated_at desc", - "alphabeticalByName": "title", -} - func (c *Controller) GetArtists(w http.ResponseWriter, r *http.Request) { var artists []*db.AlbumArtist c.DB.Find(&artists) @@ -110,14 +104,37 @@ func (c *Controller) GetAlbum(w http.ResponseWriter, r *http.Request) { respond(w, r, sub) } -func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) { +func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) { listType := getStrParam(r, "type") if listType == "" { respondError(w, r, 10, "please provide a `type` parameter") return } - orderType, ok := orderExpr[listType] - if !ok { + query := c.DB + switch listType { + case "alphabeticalByArtist": + query = query. + Joins("JOIN album_artists ON albums.album_artist_id=album_artists.id"). + Order("album_artists.name") + case "alphabeticalByName": + query = query.Order("title") + case "byYear": + query = query.Order("year") + case "frequent": + user := r.Context().Value(contextUserKey).(*db.User) + query = query. + Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?", user.ID). + Order("plays.count desc") + case "newest": + query = query.Order("updated_at desc") + case "random": + query = query.Order(gorm.Expr("random()")) + case "recent": + user := r.Context().Value(contextUserKey).(*db.User) + query = query. + Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?", user.ID). + Order("plays.time desc") + default: respondError(w, r, 10, fmt.Sprintf( "unknown value `%s` for parameter 'type'", listType, )) @@ -125,10 +142,9 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) { } size := getIntParamOr(r, "size", 10) var albums []*db.Album - c.DB. - Preload("AlbumArtist"). - Order(orderType). + query. Limit(size). + Preload("AlbumArtist"). Find(&albums) sub := subsonic.NewResponse() for _, album := range albums { diff --git a/handler/handler_sub_common.go b/handler/handler_sub_common.go index d7160b4..aad9371 100644 --- a/handler/handler_sub_common.go +++ b/handler/handler_sub_common.go @@ -30,7 +30,9 @@ func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) { return } var track db.Track - c.DB.First(&track, id) + c.DB. + Preload("Album"). + First(&track, id) if track.Path == "" { respondError(w, r, 70, fmt.Sprintf("media with id `%d` was not found", id)) return @@ -42,6 +44,17 @@ func (c *Controller) Stream(w http.ResponseWriter, r *http.Request) { } stat, _ := file.Stat() http.ServeContent(w, r, track.Path, stat.ModTime(), file) + // + // after we've served the file, mark the album as played + user := r.Context().Value(contextUserKey).(*db.User) + play := db.Play{ + AlbumID: track.Album.ID, + UserID: user.ID, + } + c.DB.Where(play).First(&play) + play.Time = time.Now() // for getAlbumList?type=recent + play.Count++ // for getAlbumList?type=frequent + c.DB.Save(&play) } func (c *Controller) GetCoverArt(w http.ResponseWriter, r *http.Request) { @@ -75,12 +88,7 @@ func (c *Controller) Scrobble(w http.ResponseWriter, r *http.Request) { return } // fetch user to get lastfm session - username := getStrParam(r, "u") - user := c.GetUserFromName(username) - if user == nil { - respondError(w, r, 10, "could not find a user with that name") - return - } + user := r.Context().Value(contextUserKey).(*db.User) if user.LastFMSession == "" { respondError(w, r, 0, fmt.Sprintf("no last.fm session for this user: %v", err)) return diff --git a/handler/middleware_sub.go b/handler/middleware_sub.go index 4d2bb23..f579e2b 100644 --- a/handler/middleware_sub.go +++ b/handler/middleware_sub.go @@ -1,6 +1,7 @@ package handler import ( + "context" "crypto/md5" "encoding/hex" "fmt" @@ -75,7 +76,8 @@ func (c *Controller) WithValidSubsonicArgs(next http.HandlerFunc) http.HandlerFu respondError(w, r, 40, "invalid password") return } - next.ServeHTTP(w, r) + withUser := context.WithValue(r.Context(), contextUserKey, user) + next.ServeHTTP(w, r.WithContext(withUser)) } }