Replaced Beego logging

This commit is contained in:
Deluan 2020-01-08 20:45:07 -05:00 committed by Deluan Quintão
parent 6eda38a951
commit 84d69a4f41
28 changed files with 559 additions and 282 deletions

View File

@ -4,9 +4,9 @@ import (
"errors" "errors"
"net/http" "net/http"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
@ -42,7 +42,7 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, err
listFunc, found := c.listFunctions[typ] listFunc, found := c.listFunctions[typ]
if !found { if !found {
beego.Error("albumList type", typ, "not implemented!") log.Error(r, "albumList type not implemented", "type", typ)
return nil, errors.New("Not implemented!") return nil, errors.New("Not implemented!")
} }
@ -51,7 +51,7 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, err
albums, err := listFunc(offset, size) albums, err := listFunc(offset, size)
if err != nil { if err != nil {
beego.Error("Error retrieving albums:", err) log.Error(r, "Error retrieving albums", "error", err)
return nil, errors.New("Internal Error") return nil, errors.New("Internal Error")
} }
@ -83,7 +83,7 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque
func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
albums, mediaFiles, err := c.listGen.GetAllStarred() albums, mediaFiles, err := c.listGen.GetAllStarred()
if err != nil { if err != nil {
beego.Error("Error retrieving starred media:", err) log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -97,7 +97,7 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request)
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
albums, mediaFiles, err := c.listGen.GetAllStarred() albums, mediaFiles, err := c.listGen.GetAllStarred()
if err != nil { if err != nil {
beego.Error("Error retrieving starred media:", err) log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -111,7 +111,7 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request
func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
npInfos, err := c.listGen.GetNowPlaying() npInfos, err := c.listGen.GetNowPlaying()
if err != nil { if err != nil {
beego.Error("Error retrieving now playing list:", err) log.Error(r, "Error retrieving now playing list", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -133,7 +133,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ
songs, err := c.listGen.GetRandomSongs(size) songs, err := c.listGen.GetRandomSongs(size)
if err != nil { if err != nil {
beego.Error("Error retrieving random songs:", err) log.Error(r, "Error retrieving random songs", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }

View File

@ -1,46 +1,47 @@
package api_test package api_test
import ( //
"fmt" //import (
"net/http" // "fmt"
"net/http/httptest" // "net/http"
"strings" // "net/http/httptest"
// "strings"
"github.com/astaxie/beego" //
) // "github.com/astaxie/beego"
//)
const ( //
testUser = "deluan" //const (
testPassword = "wordpass" // testUser = "deluan"
testClient = "test" // testPassword = "wordpass"
testVersion = "1.0.0" // testClient = "test"
) // testVersion = "1.0.0"
//)
func AddParams(endpoint string, params ...string) string { //
url := fmt.Sprintf("%s?u=%s&p=%s&c=%s&v=%s&f=json", endpoint, testUser, testPassword, testClient, testVersion) //func AddParams(endpoint string, params ...string) string {
if len(params) > 0 { // url := fmt.Sprintf("%s?u=%s&p=%s&c=%s&v=%s&f=json", endpoint, testUser, testPassword, testClient, testVersion)
url = url + "&" + strings.Join(params, "&") // if len(params) > 0 {
} // url = url + "&" + strings.Join(params, "&")
return url // }
} // return url
//}
func Get(url string, testCase string) (*http.Request, *httptest.ResponseRecorder) { //
r, _ := http.NewRequest("GET", url, nil) //func Get(url string, testCase string) (*http.Request, *httptest.ResponseRecorder) {
w := httptest.NewRecorder() // r, _ := http.NewRequest("GET", url, nil)
beego.BeeApp.Handlers.ServeHTTP(w, r) // w := httptest.NewRecorder()
// beego.BeeApp.Handlers.ServeHTTP(w, r)
beego.Debug("testing", testCase, fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%s", r.URL, w.Code, w.Body.String())) //
// log.Debug(r, "testing", testCase, fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%s", r.URL, w.Code, w.Body.String()))
return r, w //
} // return r, w
//}
func GetWithHeader(url string, header, value, testCase string) (*http.Request, *httptest.ResponseRecorder) { //
r, _ := http.NewRequest("GET", url, nil) //func GetWithHeader(url string, header, value, testCase string) (*http.Request, *httptest.ResponseRecorder) {
r.Header.Add(header, value) // r, _ := http.NewRequest("GET", url, nil)
w := httptest.NewRecorder() // r.Header.Add(header, value)
beego.BeeApp.Handlers.ServeHTTP(w, r) // w := httptest.NewRecorder()
// beego.BeeApp.Handlers.ServeHTTP(w, r)
beego.Debug("testing", testCase, fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%s", r.URL, w.Code, w.Body.String())) //
// log.Debug(r, "testing", testCase, fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%s", r.URL, w.Code, w.Body.String()))
return r, w //
} // return r, w
//}

View File

@ -5,11 +5,11 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
@ -33,10 +33,10 @@ func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Requ
return response, nil return response, nil
} }
func (c *BrowsingController) getArtistIndex(ifModifiedSince time.Time) (*responses.Indexes, error) { func (c *BrowsingController) getArtistIndex(r *http.Request, ifModifiedSince time.Time) (*responses.Indexes, error) {
indexes, lastModified, err := c.browser.Indexes(ifModifiedSince) indexes, lastModified, err := c.browser.Indexes(ifModifiedSince)
if err != nil { if err != nil {
beego.Error("Error retrieving Indexes:", err) log.Error(r, "Error retrieving Indexes", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -61,7 +61,7 @@ func (c *BrowsingController) getArtistIndex(ifModifiedSince time.Time) (*respons
func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ifModifiedSince := ParamTime(r, "ifModifiedSince", time.Time{}) ifModifiedSince := ParamTime(r, "ifModifiedSince", time.Time{})
res, err := c.getArtistIndex(ifModifiedSince) res, err := c.getArtistIndex(r, ifModifiedSince)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -72,7 +72,7 @@ func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request)
} }
func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
res, err := c.getArtistIndex(time.Time{}) res, err := c.getArtistIndex(r, time.Time{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -84,13 +84,13 @@ func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request)
func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
id := ParamString(r, "id") id := ParamString(r, "id")
dir, err := c.browser.Directory(id) dir, err := c.browser.Directory(r.Context(), id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error("Requested Id", id, "not found:", err) log.Error(r, "Requested Id not found ", "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Directory not found") return nil, NewError(responses.ErrorDataNotFound, "Directory not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -101,13 +101,13 @@ func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Re
func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
id := ParamString(r, "id") id := ParamString(r, "id")
dir, err := c.browser.Artist(id) dir, err := c.browser.Artist(r.Context(), id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error("Requested ArtistId", id, "not found:", err) log.Error(r, "Requested ArtistId not found ", "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Artist not found") return nil, NewError(responses.ErrorDataNotFound, "Artist not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -118,13 +118,13 @@ func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) (
func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
id := ParamString(r, "id") id := ParamString(r, "id")
dir, err := c.browser.Album(id) dir, err := c.browser.Album(r.Context(), id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error("Requested AlbumId", id, "not found:", err) log.Error(r, "Requested Id not found ", "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Album not found") return nil, NewError(responses.ErrorDataNotFound, "Album not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -138,10 +138,10 @@ func (c *BrowsingController) GetSong(w http.ResponseWriter, r *http.Request) (*r
song, err := c.browser.GetSong(id) song, err := c.browser.GetSong(id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error("Requested Id", id, "not found:", err) log.Error(r, "Requested Id not found ", "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Song not found") return nil, NewError(responses.ErrorDataNotFound, "Song not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }

View File

@ -1,14 +1,13 @@
package api package api
import ( import (
"fmt"
"net/http" "net/http"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
) )
type MediaAnnotationController struct { type MediaAnnotationController struct {
@ -33,15 +32,15 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req
return nil, err return nil, err
} }
beego.Debug("Setting rating", rating, "for id", id) log.Debug(r, "Setting rating", "rating", rating, "id", id)
err = c.ratings.SetRating(id, rating) err = c.ratings.SetRating(r.Context(), id, rating)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorDataNotFound, "Id not found") return nil, NewError(responses.ErrorDataNotFound, "Id not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -50,7 +49,7 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req
func (c *MediaAnnotationController) getIds(r *http.Request) ([]string, error) { func (c *MediaAnnotationController) getIds(r *http.Request) ([]string, error) {
ids := ParamStrings(r, "id") ids := ParamStrings(r, "id")
albumIds := ParamStrings(r,"albumId") albumIds := ParamStrings(r, "albumId")
if len(ids) == 0 && len(albumIds) == 0 { if len(ids) == 0 && len(albumIds) == 0 {
return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing") return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing")
@ -64,14 +63,14 @@ func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request)
if err != nil { if err != nil {
return nil, err return nil, err
} }
beego.Debug("Starring ids:", ids) log.Debug(r, "Starring items", "ids", ids)
err = c.ratings.SetStar(true, ids...) err = c.ratings.SetStar(r.Context(), true, ids...)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorDataNotFound, "Id not found") return nil, NewError(responses.ErrorDataNotFound, "Id not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -83,14 +82,14 @@ func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Reques
if err != nil { if err != nil {
return nil, err return nil, err
} }
beego.Debug("Unstarring ids:", ids) log.Debug(r, "Unstarring items", "ids", ids)
err = c.ratings.SetStar(false, ids...) err = c.ratings.SetStar(r.Context(), false, ids...)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorDataNotFound, "Directory not found") return nil, NewError(responses.ErrorDataNotFound, "Directory not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -111,7 +110,7 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ
playerName := ParamString(r, "c") playerName := ParamString(r, "c")
username := ParamString(r, "u") username := ParamString(r, "u")
beego.Debug("Scrobbling ids:", ids, "times:", times, "submission:", submission) log.Debug(r, "Scrobbling tracks", "ids", ids, "times", times, "submission", submission)
for i, id := range ids { for i, id := range ids {
var t time.Time var t time.Time
if len(times) > 0 { if len(times) > 0 {
@ -120,19 +119,19 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ
t = time.Now() t = time.Now()
} }
if submission { if submission {
mf, err := c.scrobbler.Register(playerId, id, t) mf, err := c.scrobbler.Register(r.Context(), playerId, id, t)
if err != nil { if err != nil {
beego.Error("Error scrobbling", id, "-", err) log.Error(r, "Error scrobbling track", "id", id, err)
continue continue
} }
beego.Info(fmt.Sprintf(`Scrobbled (%s) "%s" at %v`, id, mf.Title, t)) log.Info(r, "Scrobbled", "id", id, "title", mf.Title, "timestamp", t)
} else { } else {
mf, err := c.scrobbler.NowPlaying(playerId, playerName, id, username) mf, err := c.scrobbler.NowPlaying(r.Context(), playerId, playerName, id, username)
if err != nil { if err != nil {
beego.Error("Error setting", id, "as current song:", err) log.Error(r, "Error setting current song", "id", id, err)
continue continue
} }
beego.Info(fmt.Sprintf(`Now Playing (%s) "%s" at %v`, id, mf.Title, t)) log.Info(r, "Now Playing", "id", id, "title", mf.Title, "timestamp", t)
} }
} }
return NewEmpty(), nil return NewEmpty(), nil

View File

@ -5,10 +5,10 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
) )
type MediaRetrievalController struct { type MediaRetrievalController struct {
@ -23,7 +23,7 @@ func (c *MediaRetrievalController) GetAvatar(w http.ResponseWriter, r *http.Requ
var f *os.File var f *os.File
f, err := os.Open("static/itunes.png") f, err := os.Open("static/itunes.png")
if err != nil { if err != nil {
beego.Error(err, "Image not found") log.Error(r, "Image not found", err)
return nil, NewError(responses.ErrorDataNotFound, "Avatar image not found") return nil, NewError(responses.ErrorDataNotFound, "Avatar image not found")
} }
defer f.Close() defer f.Close()
@ -43,10 +43,10 @@ func (c *MediaRetrievalController) GetCoverArt(w http.ResponseWriter, r *http.Re
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error(err, "Id:", id) log.Error(r, err.Error(), "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Cover not found") return nil, NewError(responses.ErrorDataNotFound, "Cover not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }

View File

@ -1,4 +1,5 @@
package api_test package api_test
// //
//import ( //import (
// "fmt" // "fmt"
@ -20,7 +21,7 @@ package api_test
// r, _ := http.NewRequest("GET", url, nil) // r, _ := http.NewRequest("GET", url, nil)
// w := httptest.NewRecorder() // w := httptest.NewRecorder()
// beego.BeeApp.Handlers.ServeHTTP(w, r) // beego.BeeApp.Handlers.ServeHTTP(w, r)
// beego.Debug("testing TestGetCoverArt", fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%#v", r.URL, w.Code, w.HeaderMap)) // log.Debug(r, "testing TestGetCoverArt", fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%#v", r.URL, w.Code, w.HeaderMap))
// return r, w // return r, w
//} //}
// //

View File

@ -8,9 +8,9 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
) )
func checkRequiredParameters(next http.Handler) http.Handler { func checkRequiredParameters(next http.Handler) http.Handler {
@ -20,14 +20,14 @@ func checkRequiredParameters(next http.Handler) http.Handler {
for _, p := range requiredParameters { for _, p := range requiredParameters {
if ParamString(r, p) == "" { if ParamString(r, p) == "" {
msg := fmt.Sprintf(`Missing required parameter "%s"`, p) msg := fmt.Sprintf(`Missing required parameter "%s"`, p)
beego.Warn(msg) log.Warn(r, msg)
SendError(w, r, NewError(responses.ErrorMissingParameter, msg)) SendError(w, r, NewError(responses.ErrorMissingParameter, msg))
return return
} }
} }
if ParamString(r, "p") == "" && (ParamString(r, "s") == "" || ParamString(r, "t") == "") { if ParamString(r, "p") == "" && (ParamString(r, "s") == "" || ParamString(r, "t") == "") {
beego.Warn("Missing authentication information") log.Warn(r, "Missing authentication information")
} }
ctx := r.Context() ctx := r.Context()
ctx = context.WithValue(ctx, "user", ParamString(r, "u")) ctx = context.WithValue(ctx, "user", ParamString(r, "u"))
@ -63,7 +63,7 @@ func authenticate(next http.Handler) http.Handler {
} }
if user != conf.Sonic.User || !valid { if user != conf.Sonic.User || !valid {
beego.Warn(fmt.Sprintf(`Invalid login for user "%s"`, user)) log.Warn(r, "Invalid login", "user", user)
SendError(w, r, NewError(responses.ErrorAuthenticationFail)) SendError(w, r, NewError(responses.ErrorAuthenticationFail))
return return
} }

View File

@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
) )
type PlaylistsController struct { type PlaylistsController struct {
@ -21,7 +21,7 @@ func NewPlaylistsController(pls engine.Playlists) *PlaylistsController {
func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
allPls, err := c.pls.GetAll() allPls, err := c.pls.GetAll()
if err != nil { if err != nil {
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal error") return nil, NewError(responses.ErrorGeneric, "Internal error")
} }
playlists := make([]responses.Playlist, len(allPls)) playlists := make([]responses.Playlist, len(allPls))
@ -47,10 +47,10 @@ func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request
pinfo, err := c.pls.Get(id) pinfo, err := c.pls.Get(id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error(err, "Id:", id) log.Error(r, err.Error(), "id", id)
return nil, NewError(responses.ErrorDataNotFound, "Directory not found") return nil, NewError(responses.ErrorDataNotFound, "Directory not found")
case err != nil: case err != nil:
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
@ -68,9 +68,9 @@ func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Requ
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = c.pls.Create(name, songIds) err = c.pls.Create(r.Context(), name, songIds)
if err != nil { if err != nil {
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
return NewEmpty(), nil return NewEmpty(), nil
@ -81,9 +81,9 @@ func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Requ
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = c.pls.Delete(id) err = c.pls.Delete(r.Context(), id)
if err != nil { if err != nil {
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
return NewEmpty(), nil return NewEmpty(), nil
@ -103,16 +103,16 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ
pname = &s pname = &s
} }
beego.Info(fmt.Sprintf("Updating playlist with id '%s'", playlistId)) log.Info(r, "Updating playlist", "id", playlistId)
if pname != nil { if pname != nil {
beego.Debug(fmt.Sprintf("-- New Name: '%s'", *pname)) log.Debug(r, fmt.Sprintf("-- New Name: '%s'", *pname))
} }
beego.Debug(fmt.Sprintf("-- Adding: '%v'", songsToAdd)) log.Debug(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd))
beego.Debug(fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove)) log.Debug(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove))
err = c.pls.Update(playlistId, pname, songsToAdd, songIndexesToRemove) err = c.pls.Update(playlistId, pname, songsToAdd, songIndexesToRemove)
if err != nil { if err != nil {
beego.Error(err) log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
} }
return NewEmpty(), nil return NewEmpty(), nil

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
) )
type SearchingController struct { type SearchingController struct {
@ -39,21 +39,21 @@ func (c *SearchingController) getParams(r *http.Request) error {
return nil return nil
} }
func (c *SearchingController) searchAll() (engine.Entries, engine.Entries, engine.Entries) { func (c *SearchingController) searchAll(r *http.Request) (engine.Entries, engine.Entries, engine.Entries) {
as, err := c.search.SearchArtist(c.query, c.artistOffset, c.artistCount) as, err := c.search.SearchArtist(r.Context(), c.query, c.artistOffset, c.artistCount)
if err != nil { if err != nil {
beego.Error("Error searching for Artists:", err) log.Error(r, "Error searching for Artists", err)
} }
als, err := c.search.SearchAlbum(c.query, c.albumOffset, c.albumCount) als, err := c.search.SearchAlbum(r.Context(), c.query, c.albumOffset, c.albumCount)
if err != nil { if err != nil {
beego.Error("Error searching for Albums:", err) log.Error(r, "Error searching for Albums", err)
} }
mfs, err := c.search.SearchSong(c.query, c.songOffset, c.songCount) mfs, err := c.search.SearchSong(r.Context(), c.query, c.songOffset, c.songCount)
if err != nil { if err != nil {
beego.Error("Error searching for MediaFiles:", err) log.Error(r, "Error searching for MediaFiles", err)
} }
beego.Debug(fmt.Sprintf("Searching for [%s] resulted in %d songs, %d albums and %d artists", c.query, len(mfs), len(als), len(as))) log.Debug(r, fmt.Sprintf("Search resulted in %d songs, %d albums and %d artists", len(mfs), len(als), len(as)), "query", c.query)
return mfs, als, as return mfs, als, as
} }
@ -62,7 +62,7 @@ func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
mfs, als, as := c.searchAll() mfs, als, as := c.searchAll(r)
response := NewEmpty() response := NewEmpty()
searchResult2 := &responses.SearchResult2{} searchResult2 := &responses.SearchResult2{}
@ -81,7 +81,7 @@ func (c *SearchingController) Search3(w http.ResponseWriter, r *http.Request) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
mfs, als, as := c.searchAll() mfs, als, as := c.searchAll(r)
response := NewEmpty() response := NewEmpty()
searchResult3 := &responses.SearchResult3{} searchResult3 := &responses.SearchResult3{}

View File

@ -3,10 +3,10 @@ package api
import ( import (
"net/http" "net/http"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/api/responses" "github.com/cloudsonic/sonic-server/api/responses"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
@ -29,10 +29,10 @@ func (c *StreamController) Prepare(r *http.Request) (err error) {
c.mf, err = c.repo.Get(c.id) c.mf, err = c.repo.Get(c.id)
switch { switch {
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Error("MediaFile", c.id, "not found!") log.Error(r, "Mediafile not found", "id", c.id)
return NewError(responses.ErrorDataNotFound) return NewError(responses.ErrorDataNotFound)
case err != nil: case err != nil:
beego.Error("Error reading mediafile", c.id, "from the database", ":", err) log.Error(r, "Error reading mediafile from DB", "id", c.id, err)
return NewError(responses.ErrorGeneric, "Internal error") return NewError(responses.ErrorGeneric, "Internal error")
} }
return nil return nil
@ -48,8 +48,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
maxBitRate := ParamInt(r, "maxBitRate", 0) maxBitRate := ParamInt(r, "maxBitRate", 0)
maxBitRate = utils.MinInt(c.mf.BitRate, maxBitRate) maxBitRate = utils.MinInt(c.mf.BitRate, maxBitRate)
beego.Debug("Streaming file", c.id, ":", c.mf.Path) log.Debug(r, "Streaming file", "id", c.id, "path", c.mf.Path, "bitrate", c.mf.BitRate, "maxBitRate", maxBitRate)
beego.Debug("Bitrate", c.mf.BitRate, "MaxBitRate", maxBitRate)
// TODO Send proper estimated content-length // TODO Send proper estimated content-length
//contentLength := c.mf.Size //contentLength := c.mf.Size
@ -64,16 +63,16 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
h.Set("Pragma", "public") h.Set("Pragma", "public")
if r.Method == "HEAD" { if r.Method == "HEAD" {
beego.Debug("Just a HEAD. Not streaming", c.mf.Path) log.Debug(r, "Just a HEAD. Not streaming", "path", c.mf.Path)
return nil, nil return nil, nil
} }
err = engine.Stream(c.mf.Path, c.mf.BitRate, maxBitRate, w) err = engine.Stream(r.Context(), c.mf.Path, c.mf.BitRate, maxBitRate, w)
if err != nil { if err != nil {
beego.Error("Error streaming file", c.id, ":", err) log.Error(r, "Error streaming file", "id", c.id, err)
} }
beego.Debug("Finished streaming of", c.mf.Path) log.Debug(r, "Finished streaming", "path", c.mf.Path)
return nil, nil return nil, nil
} }
@ -82,14 +81,14 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re
if err != nil { if err != nil {
return nil, err return nil, err
} }
beego.Debug("Sending file", c.mf.Path) log.Debug(r, "Sending file", "path", c.mf.Path)
err = engine.Stream(c.mf.Path, 0, 0, w) err = engine.Stream(r.Context(), c.mf.Path, 0, 0, w)
if err != nil { if err != nil {
beego.Error("Error downloading file", c.mf.Path, ":", err.Error()) log.Error(r, "Error downloading file", "path", c.mf.Path, err)
} }
beego.Debug("Finished sending", c.mf.Path) log.Debug(r, "Finished sending", "path", c.mf.Path)
return nil, nil return nil, nil
} }

View File

@ -1,4 +1,5 @@
package api_test package api_test
// //
//import ( //import (
// "fmt" // "fmt"
@ -20,7 +21,7 @@ package api_test
// r, _ := http.NewRequest("GET", url, nil) // r, _ := http.NewRequest("GET", url, nil)
// w := httptest.NewRecorder() // w := httptest.NewRecorder()
// beego.BeeApp.Handlers.ServeHTTP(w, r) // beego.BeeApp.Handlers.ServeHTTP(w, r)
// beego.Debug("testing TestStream", fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%#v", r.URL, w.Code, w.HeaderMap)) // log.Debug(r, "testing TestStream", fmt.Sprintf("\nUrl: %s\nStatus Code: [%d]\n%#v", r.URL, w.Code, w.HeaderMap))
// return r, w // return r, w
//} //}
// //

View File

@ -1,4 +1,5 @@
package api_test package api_test
// //
//import ( //import (
// "encoding/xml" // "encoding/xml"
@ -92,7 +93,7 @@ package api_test
// //
//func TestContext(t *testing.T) { //func TestContext(t *testing.T) {
// tests.Init(t, false) // tests.Init(t, false)
// beego.Router("/rest/mocktest", &mockController{}) // log.Router(r, "/rest/mocktest", &mockController{})
// //
// Convey("Subject: Context", t, func() { // Convey("Subject: Context", t, func() {
// _, w := GetWithHeader("/rest/mocktest?u=deluan&p=wordpass&c=testClient&v=1.0.0", "X-Request-Id", "123123", "TestContext") // _, w := GetWithHeader("/rest/mocktest?u=deluan&p=wordpass&c=testClient&v=1.0.0", "X-Request-Id", "123123", "TestContext")

17
app.go
View File

@ -8,20 +8,18 @@ import (
"time" "time"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/scanner" "github.com/cloudsonic/sonic-server/scanner"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
"github.com/sirupsen/logrus"
) )
type App struct { type App struct {
router *chi.Mux router *chi.Mux
logger *logrus.Logger
importer *scanner.Importer importer *scanner.Importer
} }
func (a *App) Initialize() { func (a *App) Initialize() {
a.logger = logrus.New()
initMimeTypes() initMimeTypes()
a.initRoutes() a.initRoutes()
a.initImporter() a.initImporter()
@ -35,8 +33,8 @@ func (a *App) MountRouter(path string, subRouter http.Handler) {
} }
func (a *App) Run(addr string) { func (a *App) Run(addr string) {
a.logger.Info("Listening on addr ", addr) log.Info("Started CloudSonic server", "address", addr)
a.logger.Fatal(http.ListenAndServe(addr, a.router)) log.Error(http.ListenAndServe(addr, a.router))
} }
func (a *App) initRoutes() { func (a *App) initRoutes() {
@ -47,6 +45,7 @@ func (a *App) initRoutes() {
r.Use(middleware.Recoverer) r.Use(middleware.Recoverer)
r.Use(middleware.Compress(5, "application/xml", "application/json")) r.Use(middleware.Compress(5, "application/xml", "application/json"))
r.Use(middleware.Heartbeat("/ping")) r.Use(middleware.Heartbeat("/ping"))
r.Use(InjectLogger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) { r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/Jamstash", 302) http.Redirect(w, r, "/Jamstash", 302)
@ -89,3 +88,11 @@ func FileServer(r chi.Router, path string, root http.FileSystem) {
fs.ServeHTTP(w, r) fs.ServeHTTP(w, r)
})) }))
} }
func InjectLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = log.NewContext(r.Context(), "requestId", ctx.Value(middleware.RequestIDKey))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

View File

@ -1,21 +1,22 @@
package engine package engine
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
type Browser interface { type Browser interface {
MediaFolders() (domain.MediaFolders, error) MediaFolders() (domain.MediaFolders, error)
Indexes(ifModifiedSince time.Time) (domain.ArtistIndexes, time.Time, error) Indexes(ifModifiedSince time.Time) (domain.ArtistIndexes, time.Time, error)
Directory(id string) (*DirectoryInfo, error) Directory(ctx context.Context, id string) (*DirectoryInfo, error)
Artist(id string) (*DirectoryInfo, error) Artist(ctx context.Context, id string) (*DirectoryInfo, error)
Album(id string) (*DirectoryInfo, error) Album(ctx context.Context, id string) (*DirectoryInfo, error)
GetSong(id string) (*Entry, error) GetSong(id string) (*Entry, error)
} }
@ -73,32 +74,32 @@ type DirectoryInfo struct {
Genre string Genre string
} }
func (b *browser) Artist(id string) (*DirectoryInfo, error) { func (b *browser) Artist(ctx context.Context, id string) (*DirectoryInfo, error) {
beego.Debug("Found Artist with id", id)
a, albums, err := b.retrieveArtist(id) a, albums, err := b.retrieveArtist(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(ctx, "Found Artist", "id", id, "name", a.Name)
return b.buildArtistDir(a, albums), nil return b.buildArtistDir(a, albums), nil
} }
func (b *browser) Album(id string) (*DirectoryInfo, error) { func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) {
beego.Debug("Found Album with id", id)
al, tracks, err := b.retrieveAlbum(id) al, tracks, err := b.retrieveAlbum(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(ctx, "Found Album", "id", id, "name", al.Name)
return b.buildAlbumDir(al, tracks), nil return b.buildAlbumDir(al, tracks), nil
} }
func (b *browser) Directory(id string) (*DirectoryInfo, error) { func (b *browser) Directory(ctx context.Context, id string) (*DirectoryInfo, error) {
switch { switch {
case b.isArtist(id): case b.isArtist(ctx, id):
return b.Artist(id) return b.Artist(ctx, id)
case b.isAlbum(id): case b.isAlbum(ctx, id):
return b.Album(id) return b.Album(ctx, id)
default: default:
beego.Debug("Id", id, "not found") log.Debug(ctx, "Directory not found", "id", id)
return nil, domain.ErrNotFound return nil, domain.ErrNotFound
} }
} }
@ -153,19 +154,19 @@ func (b *browser) buildAlbumDir(al *domain.Album, tracks domain.MediaFiles) *Dir
return dir return dir
} }
func (b *browser) isArtist(id string) bool { func (b *browser) isArtist(ctx context.Context, id string) bool {
found, err := b.artistRepo.Exists(id) found, err := b.artistRepo.Exists(id)
if err != nil { if err != nil {
beego.Debug(fmt.Errorf("Error searching for Artist %s: %v", id, err)) log.Debug(ctx, "Error searching for Artist", "id", id, err)
return false return false
} }
return found return found
} }
func (b *browser) isAlbum(id string) bool { func (b *browser) isAlbum(ctx context.Context, id string) bool {
found, err := b.albumRepo.Exists(id) found, err := b.albumRepo.Exists(id)
if err != nil { if err != nil {
beego.Debug(fmt.Errorf("Error searching for Album %s: %v", id, err)) log.Debug(ctx, "Error searching for Album", "id", id, err)
return false return false
} }
return found return found

View File

@ -1,19 +1,19 @@
package engine package engine
import ( import (
"fmt" "context"
"sort" "sort"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/itunesbridge" "github.com/cloudsonic/sonic-server/itunesbridge"
"github.com/cloudsonic/sonic-server/log"
) )
type Playlists interface { type Playlists interface {
GetAll() (domain.Playlists, error) GetAll() (domain.Playlists, error)
Get(id string) (*PlaylistInfo, error) Get(id string) (*PlaylistInfo, error)
Create(name string, ids []string) error Create(ctx context.Context, name string, ids []string) error
Delete(playlistId string) error Delete(ctx context.Context, playlistId string) error
Update(playlistId string, name *string, idsToAdd []string, idxToRemove []int) error Update(playlistId string, name *string, idsToAdd []string, idxToRemove []int) error
} }
@ -42,21 +42,21 @@ type PlaylistInfo struct {
Comment string Comment string
} }
func (p *playlists) Create(name string, ids []string) error { func (p *playlists) Create(ctx context.Context, name string, ids []string) error {
pid, err := p.itunes.CreatePlaylist(name, ids) pid, err := p.itunes.CreatePlaylist(name, ids)
if err != nil { if err != nil {
return err return err
} }
beego.Info(fmt.Sprintf("Created playlist '%s' with id '%s'", name, pid)) log.Info(ctx, "Created playlist", "playlist", name, "id", pid)
return nil return nil
} }
func (p *playlists) Delete(playlistId string) error { func (p *playlists) Delete(ctx context.Context, playlistId string) error {
err := p.itunes.DeletePlaylist(playlistId) err := p.itunes.DeletePlaylist(playlistId)
if err != nil { if err != nil {
return err return err
} }
beego.Info(fmt.Sprintf("Deleted playlist with id '%s'", playlistId)) log.Info(ctx, "Deleted playlist", "id", playlistId)
return nil return nil
} }

View File

@ -1,15 +1,17 @@
package engine package engine
import ( import (
"github.com/astaxie/beego" "context"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/itunesbridge" "github.com/cloudsonic/sonic-server/itunesbridge"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
type Ratings interface { type Ratings interface {
SetStar(star bool, ids ...string) error SetStar(ctx context.Context, star bool, ids ...string) error
SetRating(id string, rating int) error SetRating(ctx context.Context, id string, rating int) error
} }
func NewRatings(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, alr domain.AlbumRepository, ar domain.ArtistRepository) Ratings { func NewRatings(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, alr domain.AlbumRepository, ar domain.ArtistRepository) Ratings {
@ -23,14 +25,14 @@ type ratings struct {
artistRepo domain.ArtistRepository artistRepo domain.ArtistRepository
} }
func (r ratings) SetRating(id string, rating int) error { func (r ratings) SetRating(ctx context.Context, id string, rating int) error {
rating = utils.MinInt(rating, 5) * 20 rating = utils.MinInt(rating, 5) * 20
isAlbum, _ := r.albumRepo.Exists(id) isAlbum, _ := r.albumRepo.Exists(id)
if isAlbum { if isAlbum {
mfs, _ := r.mfRepo.FindByAlbum(id) mfs, _ := r.mfRepo.FindByAlbum(id)
if len(mfs) > 0 { if len(mfs) > 0 {
beego.Debug("SetRating:", rating, "Album:", mfs[0].Album) log.Debug(ctx, "Set Rating", "value", rating, "album", mfs[0].Album)
if err := r.itunes.SetAlbumRating(mfs[0].Id, rating); err != nil { if err := r.itunes.SetAlbumRating(mfs[0].Id, rating); err != nil {
return err return err
} }
@ -43,7 +45,7 @@ func (r ratings) SetRating(id string, rating int) error {
return err return err
} }
if mf != nil { if mf != nil {
beego.Debug("SetRating:", rating, "Song:", mf.Title) log.Debug(ctx, "Set Rating", "value", rating, "song", mf.Title)
if err := r.itunes.SetTrackRating(mf.Id, rating); err != nil { if err := r.itunes.SetTrackRating(mf.Id, rating); err != nil {
return err return err
} }
@ -52,13 +54,13 @@ func (r ratings) SetRating(id string, rating int) error {
return domain.ErrNotFound return domain.ErrNotFound
} }
func (r ratings) SetStar(star bool, ids ...string) error { func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
for _, id := range ids { for _, id := range ids {
isAlbum, _ := r.albumRepo.Exists(id) isAlbum, _ := r.albumRepo.Exists(id)
if isAlbum { if isAlbum {
mfs, _ := r.mfRepo.FindByAlbum(id) mfs, _ := r.mfRepo.FindByAlbum(id)
if len(mfs) > 0 { if len(mfs) > 0 {
beego.Debug("SetStar:", star, "Album:", mfs[0].Album) log.Debug(ctx, "Set Star", "value", star, "album", mfs[0].Album)
if err := r.itunes.SetAlbumLoved(mfs[0].Id, star); err != nil { if err := r.itunes.SetAlbumLoved(mfs[0].Id, star); err != nil {
return err return err
} }
@ -71,7 +73,7 @@ func (r ratings) SetStar(star bool, ids ...string) error {
return err return err
} }
if mf != nil { if mf != nil {
beego.Debug("SetStar:", star, "Song:", mf.Title) log.Debug(ctx, "Set Star", "value", star, "song", mf.Title)
if err := r.itunes.SetTrackLoved(mf.Id, star); err != nil { if err := r.itunes.SetTrackLoved(mf.Id, star); err != nil {
return err return err
} }

View File

@ -1,13 +1,14 @@
package engine package engine
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/itunesbridge" "github.com/cloudsonic/sonic-server/itunesbridge"
"github.com/cloudsonic/sonic-server/log"
) )
const ( const (
@ -16,8 +17,8 @@ const (
) )
type Scrobbler interface { type Scrobbler interface {
Register(playerId int, trackId string, playDate time.Time) (*domain.MediaFile, error) Register(ctx context.Context, playerId int, trackId string, playDate time.Time) (*domain.MediaFile, error)
NowPlaying(playerId int, playerName, trackId, username string) (*domain.MediaFile, error) NowPlaying(ctx context.Context, playerId int, playerName, trackId, username string) (*domain.MediaFile, error)
} }
func NewScrobbler(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, npr NowPlayingRepository) Scrobbler { func NewScrobbler(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, npr NowPlayingRepository) Scrobbler {
@ -30,7 +31,7 @@ type scrobbler struct {
npRepo NowPlayingRepository npRepo NowPlayingRepository
} }
func (s *scrobbler) detectSkipped(playerId int, trackId string) { func (s *scrobbler) detectSkipped(ctx context.Context, playerId int, trackId string) {
size, _ := s.npRepo.Count(playerId) size, _ := s.npRepo.Count(playerId)
switch size { switch size {
case 0: case 0:
@ -53,22 +54,22 @@ func (s *scrobbler) detectSkipped(playerId int, trackId string) {
} }
diff := np.Start.Sub(prev.Start) diff := np.Start.Sub(prev.Start)
if diff < minSkipped || diff > maxSkipped { if diff < minSkipped || diff > maxSkipped {
beego.Debug(fmt.Sprintf("-- Playtime for track %s was %v. Not skipping.", prev.TrackId, diff)) log.Debug(ctx, fmt.Sprintf("-- Playtime for track %s was %v. Not skipping.", prev.TrackId, diff))
prev = np prev = np
continue continue
} }
err = s.itunes.MarkAsSkipped(prev.TrackId, prev.Start.Add(1*time.Minute)) err = s.itunes.MarkAsSkipped(prev.TrackId, prev.Start.Add(1*time.Minute))
if err != nil { if err != nil {
beego.Warn("Error skipping track", prev.TrackId) log.Warn(ctx, "Error skipping track", "id", prev.TrackId)
} else { } else {
beego.Debug("-- Skipped track", prev.TrackId) log.Debug(ctx, "-- Skipped track "+prev.TrackId)
} }
} }
} }
} }
func (s *scrobbler) Register(playerId int, trackId string, playTime time.Time) (*domain.MediaFile, error) { func (s *scrobbler) Register(ctx context.Context, playerId int, trackId string, playTime time.Time) (*domain.MediaFile, error) {
s.detectSkipped(playerId, trackId) s.detectSkipped(ctx, playerId, trackId)
mf, err := s.mfRepo.Get(trackId) mf, err := s.mfRepo.Get(trackId)
if err != nil { if err != nil {
@ -85,7 +86,7 @@ func (s *scrobbler) Register(playerId int, trackId string, playTime time.Time) (
return mf, nil return mf, nil
} }
func (s *scrobbler) NowPlaying(playerId int, playerName, trackId, username string) (*domain.MediaFile, error) { func (s *scrobbler) NowPlaying(ctx context.Context, playerId int, playerName, trackId, username string) (*domain.MediaFile, error) {
mf, err := s.mfRepo.Get(trackId) mf, err := s.mfRepo.Get(trackId)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -27,7 +27,7 @@ func TestScrobbler(t *testing.T) {
Convey("When I scrobble an existing song", func() { Convey("When I scrobble an existing song", func() {
now := time.Now() now := time.Now()
mf, err := scrobbler.Register(1, "2", now) mf, err := scrobbler.Register(nil, 1, "2", now)
Convey("Then I get the scrobbled song back", func() { Convey("Then I get the scrobbled song back", func() {
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -42,7 +42,7 @@ func TestScrobbler(t *testing.T) {
}) })
Convey("When the ID is not in the DB", func() { Convey("When the ID is not in the DB", func() {
_, err := scrobbler.Register(1, "3", time.Now()) _, err := scrobbler.Register(nil, 1, "3", time.Now())
Convey("Then I receive an error", func() { Convey("Then I receive an error", func() {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -54,7 +54,7 @@ func TestScrobbler(t *testing.T) {
}) })
Convey("When I inform the song that is now playing", func() { Convey("When I inform the song that is now playing", func() {
mf, err := scrobbler.NowPlaying(1, "DSub", "2", "deluan") mf, err := scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
Convey("Then I get the song for that id back", func() { Convey("Then I get the song for that id back", func() {
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -100,11 +100,11 @@ func TestSkipping(t *testing.T) {
npRepo.ClearAll() npRepo.ClearAll()
Convey("When I skip 2 songs", func() { Convey("When I skip 2 songs", func() {
npRepo.OverrideNow(aPointInTime) npRepo.OverrideNow(aPointInTime)
scrobbler.NowPlaying(1, "DSub", "1", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "1", "deluan")
npRepo.OverrideNow(aPointInTime.Add(2 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(2 * time.Second))
scrobbler.NowPlaying(1, "DSub", "3", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "3", "deluan")
npRepo.OverrideNow(aPointInTime.Add(3 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(3 * time.Second))
scrobbler.NowPlaying(1, "DSub", "2", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
Convey("Then the NowPlaying song should be the last one", func() { Convey("Then the NowPlaying song should be the last one", func() {
np, err := npRepo.GetAll() np, err := npRepo.GetAll()
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -114,12 +114,12 @@ func TestSkipping(t *testing.T) {
}) })
Convey("When I play one song", func() { Convey("When I play one song", func() {
npRepo.OverrideNow(aPointInTime) npRepo.OverrideNow(aPointInTime)
scrobbler.NowPlaying(1, "DSub", "1", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "1", "deluan")
Convey("And I skip it before 20 seconds", func() { Convey("And I skip it before 20 seconds", func() {
npRepo.OverrideNow(aPointInTime.Add(7 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(7 * time.Second))
scrobbler.NowPlaying(1, "DSub", "2", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
Convey("Then the first song should be marked as skipped", func() { Convey("Then the first song should be marked as skipped", func() {
mf, err := scrobbler.Register(1, "2", aPointInTime.Add(3*time.Minute)) mf, err := scrobbler.Register(nil, 1, "2", aPointInTime.Add(3*time.Minute))
So(mf.Id, ShouldEqual, "2") So(mf.Id, ShouldEqual, "2")
So(itCtrl.skipped, ShouldContainKey, "1") So(itCtrl.skipped, ShouldContainKey, "1")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -127,9 +127,9 @@ func TestSkipping(t *testing.T) {
}) })
Convey("And I skip it before 3 seconds", func() { Convey("And I skip it before 3 seconds", func() {
npRepo.OverrideNow(aPointInTime.Add(2 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(2 * time.Second))
scrobbler.NowPlaying(1, "DSub", "2", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
Convey("Then the first song should be marked as skipped", func() { Convey("Then the first song should be marked as skipped", func() {
mf, err := scrobbler.Register(1, "2", aPointInTime.Add(3*time.Minute)) mf, err := scrobbler.Register(nil, 1, "2", aPointInTime.Add(3*time.Minute))
So(mf.Id, ShouldEqual, "2") So(mf.Id, ShouldEqual, "2")
So(itCtrl.skipped, ShouldBeEmpty) So(itCtrl.skipped, ShouldBeEmpty)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -137,16 +137,16 @@ func TestSkipping(t *testing.T) {
}) })
Convey("And I skip it after 20 seconds", func() { Convey("And I skip it after 20 seconds", func() {
npRepo.OverrideNow(aPointInTime.Add(30 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(30 * time.Second))
scrobbler.NowPlaying(1, "DSub", "2", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
Convey("Then the first song should be marked as skipped", func() { Convey("Then the first song should be marked as skipped", func() {
mf, err := scrobbler.Register(1, "2", aPointInTime.Add(3*time.Minute)) mf, err := scrobbler.Register(nil, 1, "2", aPointInTime.Add(3*time.Minute))
So(mf.Id, ShouldEqual, "2") So(mf.Id, ShouldEqual, "2")
So(itCtrl.skipped, ShouldBeEmpty) So(itCtrl.skipped, ShouldBeEmpty)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
}) })
Convey("And I scrobble it before starting to play the other song", func() { Convey("And I scrobble it before starting to play the other song", func() {
mf, err := scrobbler.Register(1, "1", time.Now()) mf, err := scrobbler.Register(nil, 1, "1", time.Now())
Convey("Then the first song should NOT marked as skipped", func() { Convey("Then the first song should NOT marked as skipped", func() {
So(mf.Id, ShouldEqual, "1") So(mf.Id, ShouldEqual, "1")
So(itCtrl.skipped, ShouldBeEmpty) So(itCtrl.skipped, ShouldBeEmpty)
@ -156,10 +156,10 @@ func TestSkipping(t *testing.T) {
}) })
Convey("When the NowPlaying for the next song happens before the Scrobble", func() { Convey("When the NowPlaying for the next song happens before the Scrobble", func() {
npRepo.OverrideNow(aPointInTime) npRepo.OverrideNow(aPointInTime)
scrobbler.NowPlaying(1, "DSub", "1", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "1", "deluan")
npRepo.OverrideNow(aPointInTime.Add(10 * time.Second)) npRepo.OverrideNow(aPointInTime.Add(10 * time.Second))
scrobbler.NowPlaying(1, "DSub", "2", "deluan") scrobbler.NowPlaying(nil, 1, "DSub", "2", "deluan")
scrobbler.Register(1, "1", aPointInTime.Add(10*time.Minute)) scrobbler.Register(nil, 1, "1", aPointInTime.Add(10*time.Minute))
Convey("Then the NowPlaying song should be the last one", func() { Convey("Then the NowPlaying song should be the last one", func() {
np, _ := npRepo.GetAll() np, _ := npRepo.GetAll()
So(np, ShouldHaveLength, 1) So(np, ShouldHaveLength, 1)

View File

@ -1,10 +1,11 @@
package engine package engine
import ( import (
"context"
"strings" "strings"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/log"
"github.com/deluan/gomate" "github.com/deluan/gomate"
"github.com/kennygrant/sanitize" "github.com/kennygrant/sanitize"
) )
@ -19,9 +20,9 @@ type Search interface {
RemoveAlbum(ids ...string) error RemoveAlbum(ids ...string) error
RemoveMediaFile(ids ...string) error RemoveMediaFile(ids ...string) error
SearchArtist(q string, offset int, size int) (Entries, error) SearchArtist(ctx context.Context, q string, offset int, size int) (Entries, error)
SearchAlbum(q string, offset int, size int) (Entries, error) SearchAlbum(ctx context.Context, q string, offset int, size int) (Entries, error)
SearchSong(q string, offset int, size int) (Entries, error) SearchSong(ctx context.Context, q string, offset int, size int) (Entries, error)
} }
type search struct { type search struct {
@ -84,7 +85,7 @@ func (s *search) RemoveMediaFile(ids ...string) error {
return s.idxSong.Remove(ids...) return s.idxSong.Remove(ids...)
} }
func (s *search) SearchArtist(q string, offset int, size int) (Entries, error) { func (s *search) SearchArtist(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*"))) q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
min := offset min := offset
max := min + size - 1 max := min + size - 1
@ -95,7 +96,7 @@ func (s *search) SearchArtist(q string, offset int, size int) (Entries, error) {
res := make(Entries, 0, len(resp)) res := make(Entries, 0, len(resp))
for _, id := range resp { for _, id := range resp {
a, err := s.artistRepo.Get(id) a, err := s.artistRepo.Get(id)
if criticalError("Artist", id, err) { if criticalError(ctx, "Artist", id, err) {
return nil, err return nil, err
} }
if err == nil { if err == nil {
@ -105,7 +106,7 @@ func (s *search) SearchArtist(q string, offset int, size int) (Entries, error) {
return res, nil return res, nil
} }
func (s *search) SearchAlbum(q string, offset int, size int) (Entries, error) { func (s *search) SearchAlbum(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*"))) q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
min := offset min := offset
max := min + size - 1 max := min + size - 1
@ -116,7 +117,7 @@ func (s *search) SearchAlbum(q string, offset int, size int) (Entries, error) {
res := make(Entries, 0, len(resp)) res := make(Entries, 0, len(resp))
for _, id := range resp { for _, id := range resp {
al, err := s.albumRepo.Get(id) al, err := s.albumRepo.Get(id)
if criticalError("Album", id, err) { if criticalError(ctx, "Album", id, err) {
return nil, err return nil, err
} }
if err == nil { if err == nil {
@ -126,7 +127,7 @@ func (s *search) SearchAlbum(q string, offset int, size int) (Entries, error) {
return res, nil return res, nil
} }
func (s *search) SearchSong(q string, offset int, size int) (Entries, error) { func (s *search) SearchSong(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*"))) q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
min := offset min := offset
max := min + size - 1 max := min + size - 1
@ -137,7 +138,7 @@ func (s *search) SearchSong(q string, offset int, size int) (Entries, error) {
res := make(Entries, 0, len(resp)) res := make(Entries, 0, len(resp))
for _, id := range resp { for _, id := range resp {
mf, err := s.mfileRepo.Get(id) mf, err := s.mfileRepo.Get(id)
if criticalError("Song", id, err) { if criticalError(ctx, "Song", id, err) {
return nil, err return nil, err
} }
if err == nil { if err == nil {
@ -147,12 +148,12 @@ func (s *search) SearchSong(q string, offset int, size int) (Entries, error) {
return res, nil return res, nil
} }
func criticalError(kind, id string, err error) bool { func criticalError(ctx context.Context, kind, id string, err error) bool {
switch { switch {
case err != nil: case err != nil:
return true return true
case err == domain.ErrNotFound: case err == domain.ErrNotFound:
beego.Warn(kind, "Id", id, "not in the DB. Need a reindex?") log.Warn(ctx, kind+"Id not in DB. Need a reindex?", "id", id)
} }
return false return false
} }

View File

@ -1,41 +1,42 @@
package engine package engine
import ( import (
"context"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
) )
// TODO Encapsulate as a io.Reader // TODO Encapsulate as a io.Reader
func Stream(path string, bitRate int, maxBitRate int, w io.Writer) error { func Stream(ctx context.Context, path string, bitRate int, maxBitRate int, w io.Writer) error {
var f io.Reader var f io.Reader
var err error var err error
enabled := !conf.Sonic.DisableDownsampling enabled := !conf.Sonic.DisableDownsampling
if enabled && maxBitRate > 0 && bitRate > maxBitRate { if enabled && maxBitRate > 0 && bitRate > maxBitRate {
f, err = downsample(path, maxBitRate) f, err = downsample(ctx, path, maxBitRate)
} else { } else {
f, err = os.Open(path) f, err = os.Open(path)
} }
if err != nil { if err != nil {
beego.Error("Error opening file", path, ":", err) log.Error(ctx, "Error opening file", "path", path, err)
return err return err
} }
if _, err = io.Copy(w, f); err != nil { if _, err = io.Copy(w, f); err != nil {
beego.Error("Error copying file", path, ":", err) log.Error(ctx, "Error copying file", "path", path, err)
return err return err
} }
return err return err
} }
func downsample(path string, maxBitRate int) (f io.Reader, err error) { func downsample(ctx context.Context, path string, maxBitRate int) (f io.Reader, err error) {
cmdLine, args := createDownsamplingCommand(path, maxBitRate) cmdLine, args := createDownsamplingCommand(path, maxBitRate)
beego.Debug("Executing cmd:", cmdLine, args) log.Debug(ctx, "Executing command", "cmdLine", cmdLine, "args", args)
cmd := exec.Command(cmdLine, args...) cmd := exec.Command(cmdLine, args...)
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if f, err = cmd.StdoutPipe(); err != nil { if f, err = cmd.StdoutPipe(); err != nil {

1
go.mod
View File

@ -4,7 +4,6 @@ go 1.13
require ( require (
github.com/BurntSushi/toml v0.3.0 // indirect github.com/BurntSushi/toml v0.3.0 // indirect
github.com/astaxie/beego v1.8.0
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
github.com/deluan/gomate v0.0.0-20160327212459-3eb40643dd6f github.com/deluan/gomate v0.0.0-20160327212459-3eb40643dd6f
github.com/dhowden/itl v0.0.0-20170329215456-9fbe21093131 github.com/dhowden/itl v0.0.0-20170329215456-9fbe21093131

2
go.sum
View File

@ -1,7 +1,5 @@
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/astaxie/beego v1.8.0 h1:Rc5qRXMy5fpxq3FEi+4nmykYIMtANthRJ8hcoY+1VWM=
github.com/astaxie/beego v1.8.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=

167
log/log.go Normal file
View File

@ -0,0 +1,167 @@
package log
import (
"context"
"errors"
"net/http"
"os"
"strings"
"github.com/sirupsen/logrus"
)
type Level uint8
type LevelFunc = func(ctx interface{}, msg interface{}, keyValuePairs ...interface{})
const (
LevelCritical = Level(logrus.FatalLevel)
LevelError = Level(logrus.ErrorLevel)
LevelWarn = Level(logrus.WarnLevel)
LevelInfo = Level(logrus.InfoLevel)
LevelDebug = Level(logrus.DebugLevel)
)
var (
currentLevel Level
defaultLogger = logrus.New()
)
// SetLevel sets the global log level used by the simple logger.
func SetLevel(l Level) {
currentLevel = l
logrus.SetLevel(logrus.Level(l))
}
func NewContext(ctx context.Context, keyValuePairs ...interface{}) context.Context {
if ctx == nil {
ctx = context.Background()
}
logger := addFields(createNewLogger(), keyValuePairs)
ctx = context.WithValue(ctx, "logger", logger)
return ctx
}
func SetDefaultLogger(l *logrus.Logger) {
defaultLogger = l
}
func CurrentLevel() Level {
return currentLevel
}
func Error(args ...interface{}) {
if currentLevel < LevelError {
return
}
logger, msg := parseArgs(args)
logger.Error(msg)
}
func Warn(args ...interface{}) {
if currentLevel < LevelWarn {
return
}
logger, msg := parseArgs(args)
logger.Warn(msg)
}
func Info(args ...interface{}) {
if currentLevel < LevelInfo {
return
}
logger, msg := parseArgs(args)
logger.Info(msg)
}
func Debug(args ...interface{}) {
if currentLevel < LevelDebug {
return
}
logger, msg := parseArgs(args)
logger.Debug(msg)
}
func parseArgs(args []interface{}) (*logrus.Entry, string) {
var l *logrus.Entry
var err error
if args[0] == nil {
l = createNewLogger()
args = args[1:]
} else {
l, err = extractLogger(args[0])
if err != nil {
l = createNewLogger()
} else {
args = args[1:]
}
}
if len(args) > 1 {
kvPairs := args[1:]
l = addFields(l, kvPairs)
}
switch msg := args[0].(type) {
case error:
return l, msg.Error()
case string:
return l, msg
}
return l, ""
}
func addFields(logger *logrus.Entry, keyValuePairs []interface{}) *logrus.Entry {
for i := 0; i < len(keyValuePairs); i += 2 {
switch name := keyValuePairs[i].(type) {
case error:
logger = logger.WithField("error", name.Error())
case string:
value := keyValuePairs[i+1]
logger = logger.WithField(name, value)
}
}
return logger
}
func extractLogger(ctx interface{}) (*logrus.Entry, error) {
switch ctx := ctx.(type) {
case *logrus.Entry:
return ctx, nil
case context.Context:
logger := ctx.Value("logger")
if logger != nil {
return logger.(*logrus.Entry), nil
}
case *http.Request:
return extractLogger(ctx.Context())
}
return nil, errors.New("no logger found")
}
func createNewLogger() *logrus.Entry {
//l.Formatter = &logrus.TextFormatter{ForceColors: true, DisableTimestamp: false, FullTimestamp: true}
defaultLogger.Level = logrus.Level(currentLevel)
logger := logrus.NewEntry(defaultLogger)
logger.Level = logrus.Level(currentLevel)
return logger
}
func init() {
//logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, DisableTimestamp: false, FullTimestamp: true})
envLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
var level Level
switch envLevel {
case "critical":
level = LevelCritical
case "error":
level = LevelError
case "warn":
level = LevelWarn
case "debug":
level = LevelDebug
default:
level = LevelInfo
}
SetLevel(level)
}

104
log/log_test.go Normal file
View File

@ -0,0 +1,104 @@
package log
import (
"context"
"errors"
"net/http/httptest"
"testing"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
. "github.com/smartystreets/goconvey/convey"
)
func TestLog(t *testing.T) {
Convey("Test Logger", t, func() {
l, hook := test.NewNullLogger()
SetLevel(LevelInfo)
SetDefaultLogger(l)
Convey("Plain message", func() {
Error("Simple Message")
So(hook.LastEntry().Message, ShouldEqual, "Simple Message")
So(hook.LastEntry().Data, ShouldBeEmpty)
})
Convey("Passing nil as context", func() {
Error(nil, "Simple Message")
So(hook.LastEntry().Message, ShouldEqual, "Simple Message")
So(hook.LastEntry().Data, ShouldBeEmpty)
})
Convey("Message with two kv pairs", func() {
Error("Simple Message", "key1", "value1", "key2", "value2")
So(hook.LastEntry().Message, ShouldEqual, "Simple Message")
So(hook.LastEntry().Data["key1"], ShouldEqual, "value1")
So(hook.LastEntry().Data["key2"], ShouldEqual, "value2")
So(hook.LastEntry().Data, ShouldHaveLength, 2)
})
Convey("Only error", func() {
Error(errors.New("error test"))
So(hook.LastEntry().Message, ShouldEqual, "error test")
So(hook.LastEntry().Data, ShouldBeEmpty)
})
Convey("Error as last argument", func() {
Error("Error scrobbling track", "id", 1, errors.New("some issue"))
So(hook.LastEntry().Message, ShouldEqual, "Error scrobbling track")
So(hook.LastEntry().Data["id"], ShouldEqual, 1)
So(hook.LastEntry().Data["error"], ShouldEqual, "some issue")
So(hook.LastEntry().Data, ShouldHaveLength, 2)
})
Convey("Passing a request", func() {
ctx := NewContext(nil, "foo", "bar")
req := httptest.NewRequest("get", "/", nil).WithContext(ctx)
Error(req, "Simple Message", "key1", "value1")
So(hook.LastEntry().Message, ShouldEqual, "Simple Message")
So(hook.LastEntry().Data["foo"], ShouldEqual, "bar")
So(hook.LastEntry().Data["key1"], ShouldEqual, "value1")
So(hook.LastEntry().Data, ShouldHaveLength, 2)
})
Convey("Skip if level is lower", func() {
SetLevel(LevelError)
Info("Simple Message")
So(hook.LastEntry(), ShouldBeNil)
})
})
Convey("Test extractLogger", t, func() {
Convey("It returns an error if the context is nil", func() {
_, err := extractLogger(nil)
So(err, ShouldNotBeNil)
})
Convey("It returns an error if the context is a string", func() {
_, err := extractLogger("any msg")
So(err, ShouldNotBeNil)
})
Convey("It returns the logger from context if it has one", func() {
logger := logrus.NewEntry(logrus.New())
ctx := context.Background()
ctx = context.WithValue(ctx, "logger", logger)
l, err := extractLogger(ctx)
So(err, ShouldBeNil)
So(l, ShouldEqual, logger)
})
Convey("It returns the logger from request's context if it has one", func() {
logger := logrus.NewEntry(logrus.New())
ctx := context.Background()
ctx = context.WithValue(ctx, "logger", logger)
req := httptest.NewRequest("get", "/", nil).WithContext(ctx)
l, err := extractLogger(req)
So(err, ShouldBeNil)
So(l, ShouldEqual, logger)
})
})
}

View File

@ -3,7 +3,7 @@ package persistence
import ( import (
"errors" "errors"
"github.com/astaxie/beego" "github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/scanner" "github.com/cloudsonic/sonic-server/scanner"
"github.com/siddontang/ledisdb/ledis" "github.com/siddontang/ledisdb/ledis"
) )
@ -27,12 +27,12 @@ func (r *checkSumRepository) loadData() {
pairs, err := Db().HGetAll(checkSumKeyName) pairs, err := Db().HGetAll(checkSumKeyName)
if err != nil { if err != nil {
beego.Error("Error loading CheckSums:", err) log.Error("Error loading CheckSums", err)
} }
for _, p := range pairs { for _, p := range pairs {
r.data[string(p.Field)] = string(p.Value) r.data[string(p.Field)] = string(p.Value)
} }
beego.Debug("Loaded", len(r.data), "checksums") log.Debug("Loaded checksums", "total", len(r.data))
} }
func (r *checkSumRepository) Put(id, sum string) error { func (r *checkSumRepository) Put(id, sum string) error {

View File

@ -7,10 +7,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/engine"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/utils" "github.com/cloudsonic/sonic-server/utils"
) )
@ -64,7 +64,7 @@ func (i *Importer) startImport() {
go func() { go func() {
info, err := os.Stat(i.mediaFolder) info, err := os.Stat(i.mediaFolder)
if err != nil { if err != nil {
beego.Error(err) log.Error(err)
return return
} }
if i.lastCheck.After(info.ModTime()) { if i.lastCheck.After(info.ModTime()) {
@ -80,39 +80,39 @@ func (i *Importer) scan() {
i.lastScan = i.lastModifiedSince() i.lastScan = i.lastModifiedSince()
if i.lastScan.IsZero() { if i.lastScan.IsZero() {
beego.Info("Starting first iTunes Library scan. This can take a while...") log.Info("Starting first iTunes Library scan. This can take a while...")
} }
total, err := i.scanner.ScanLibrary(i.lastScan, i.mediaFolder) total, err := i.scanner.ScanLibrary(i.lastScan, i.mediaFolder)
if err != nil { if err != nil {
beego.Error("Error importing iTunes Library:", err) log.Error("Error importing iTunes Library", err)
return return
} }
beego.Debug("Found", total, "tracks,", log.Debug("Found", "tracks", total,
len(i.scanner.MediaFiles()), "songs,", "songs", len(i.scanner.MediaFiles()),
len(i.scanner.Albums()), "albums,", "albums", len(i.scanner.Albums()),
len(i.scanner.Artists()), "artists", "artists", len(i.scanner.Artists()),
len(i.scanner.Playlists()), "playlists") "playlists", len(i.scanner.Playlists()))
if err := i.importLibrary(); err != nil { if err := i.importLibrary(); err != nil {
beego.Error("Error persisting data:", err) log.Error("Error persisting data", err)
} }
if i.lastScan.IsZero() { if i.lastScan.IsZero() {
beego.Info("Finished first iTunes Library import") log.Info("Finished first iTunes Library import")
} else { } else {
beego.Debug("Finished updating tracks from iTunes Library") log.Debug("Finished updating tracks from iTunes Library")
} }
} }
func (i *Importer) lastModifiedSince() time.Time { func (i *Importer) lastModifiedSince() time.Time {
ms, err := i.propertyRepo.Get(engine.PropLastScan) ms, err := i.propertyRepo.Get(engine.PropLastScan)
if err != nil { if err != nil {
beego.Warn("Couldn't read LastScan:", err) log.Warn("Couldn't read LastScan", err)
return time.Time{} return time.Time{}
} }
if ms == "" { if ms == "" {
beego.Debug("First scan") log.Debug("First scan")
return time.Time{} return time.Time{}
} }
s, _ := strconv.ParseInt(ms, 10, 64) s, _ := strconv.ParseInt(ms, 10, 64)
@ -125,31 +125,31 @@ func (i *Importer) importLibrary() (err error) {
mfc, _ := i.mfRepo.CountAll() mfc, _ := i.mfRepo.CountAll()
plc, _ := i.plsRepo.CountAll() plc, _ := i.plsRepo.CountAll()
beego.Debug("Saving updated data") log.Debug("Saving updated data")
mfs, mfu := i.importMediaFiles() mfs, mfu := i.importMediaFiles()
als, alu := i.importAlbums() als, alu := i.importAlbums()
ars := i.importArtists() ars := i.importArtists()
pls := i.importPlaylists() pls := i.importPlaylists()
i.importArtistIndex() i.importArtistIndex()
beego.Debug("Purging old data") log.Debug("Purging old data")
if deleted, err := i.mfRepo.PurgeInactive(mfs); err != nil { if deleted, err := i.mfRepo.PurgeInactive(mfs); err != nil {
beego.Error(err) log.Error(err)
} else { } else {
i.search.RemoveMediaFile(deleted...) i.search.RemoveMediaFile(deleted...)
} }
if deleted, err := i.albumRepo.PurgeInactive(als); err != nil { if deleted, err := i.albumRepo.PurgeInactive(als); err != nil {
beego.Error(err) log.Error(err)
} else { } else {
i.search.RemoveAlbum(deleted...) i.search.RemoveAlbum(deleted...)
} }
if deleted, err := i.artistRepo.PurgeInactive(ars); err != nil { if deleted, err := i.artistRepo.PurgeInactive(ars); err != nil {
beego.Error(err) log.Error(err)
} else { } else {
i.search.RemoveArtist(deleted...) i.search.RemoveArtist(deleted...)
} }
if _, err := i.plsRepo.PurgeInactive(pls); err != nil { if _, err := i.plsRepo.PurgeInactive(pls); err != nil {
beego.Error(err) log.Error(err)
} }
arc2, _ := i.artistRepo.CountAll() arc2, _ := i.artistRepo.CountAll()
@ -158,16 +158,16 @@ func (i *Importer) importLibrary() (err error) {
plc2, _ := i.plsRepo.CountAll() plc2, _ := i.plsRepo.CountAll()
if arc != arc2 || alc != alc2 || mfc != mfc2 || plc != plc2 { if arc != arc2 || alc != alc2 || mfc != mfc2 || plc != plc2 {
beego.Info(fmt.Sprintf("Updated library totals: %d(%+d) artists, %d(%+d) albums, %d(%+d) songs, %d(%+d) playlists", arc2, arc2-arc, alc2, alc2-alc, mfc2, mfc2-mfc, plc2, plc2-plc)) log.Info(fmt.Sprintf("Updated library totals: %d(%+d) artists, %d(%+d) albums, %d(%+d) songs, %d(%+d) playlists", arc2, arc2-arc, alc2, alc2-alc, mfc2, mfc2-mfc, plc2, plc2-plc))
} }
if alu > 0 || mfu > 0 { if alu > 0 || mfu > 0 {
beego.Info(fmt.Sprintf("Updated items: %d album(s), %d song(s)", alu, mfu)) log.Info(fmt.Sprintf("Updated items: %d album(s), %d song(s)", alu, mfu))
} }
if err == nil { if err == nil {
millis := time.Now().UnixNano() / int64(time.Millisecond) millis := time.Now().UnixNano() / int64(time.Millisecond)
i.propertyRepo.Put(engine.PropLastScan, fmt.Sprint(millis)) i.propertyRepo.Put(engine.PropLastScan, fmt.Sprint(millis))
beego.Debug("LastScan timestamp:", millis) log.Debug("LastScan", "timestamp", millis)
} }
return err return err
@ -192,14 +192,14 @@ func (i *Importer) importMediaFiles() (domain.MediaFiles, int) {
} }
} }
if err := i.mfRepo.Put(mf); err != nil { if err := i.mfRepo.Put(mf); err != nil {
beego.Error(err) log.Error(err)
} }
if err := i.search.IndexMediaFile(mf); err != nil { if err := i.search.IndexMediaFile(mf); err != nil {
beego.Error("Error indexing artist:", err) log.Error("Error indexing artist", err)
} }
updates++ updates++
if !i.lastScan.IsZero() { if !i.lastScan.IsZero() {
beego.Debug(fmt.Sprintf(`-- Updated Track: "%s"`, mf.Title)) log.Debug(fmt.Sprintf(`-- Updated Track: "%s"`, mf.Title))
} }
} }
return mfs, updates return mfs, updates
@ -224,14 +224,14 @@ func (i *Importer) importAlbums() (domain.Albums, int) {
} }
} }
if err := i.albumRepo.Put(al); err != nil { if err := i.albumRepo.Put(al); err != nil {
beego.Error(err) log.Error(err)
} }
if err := i.search.IndexAlbum(al); err != nil { if err := i.search.IndexAlbum(al); err != nil {
beego.Error("Error indexing artist:", err) log.Error("Error indexing artist", err)
} }
updates++ updates++
if !i.lastScan.IsZero() { if !i.lastScan.IsZero() {
beego.Debug(fmt.Sprintf(`-- Updated Album: "%s" from "%s"`, al.Name, al.Artist)) log.Debug(fmt.Sprintf(`-- Updated Album: "%s" from "%s"`, al.Name, al.Artist))
} }
} }
return als, updates return als, updates
@ -244,10 +244,10 @@ func (i *Importer) importArtists() domain.Artists {
ars[j] = *ar ars[j] = *ar
j++ j++
if err := i.artistRepo.Put(ar); err != nil { if err := i.artistRepo.Put(ar); err != nil {
beego.Error(err) log.Error(err)
} }
if err := i.search.IndexArtist(ar); err != nil { if err := i.search.IndexArtist(ar); err != nil {
beego.Error("Error indexing artist:", err) log.Error("Error indexing artist", err)
} }
} }
return ars return ars
@ -262,7 +262,7 @@ func (i *Importer) importArtistIndex() {
} }
if err := i.saveIndex(artistIndex); err != nil { if err := i.saveIndex(artistIndex); err != nil {
beego.Error(err) log.Error(err)
} }
} }
@ -276,7 +276,7 @@ func (i *Importer) importPlaylists() domain.Playlists {
pls[j] = *pl pls[j] = *pl
j++ j++
if err := i.plsRepo.Put(pl); err != nil { if err := i.plsRepo.Put(pl); err != nil {
beego.Error(err) log.Error(err)
} }
} }
return pls return pls

View File

@ -13,9 +13,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/domain" "github.com/cloudsonic/sonic-server/domain"
"github.com/cloudsonic/sonic-server/log"
"github.com/dhowden/itl" "github.com/dhowden/itl"
"github.com/dhowden/tag" "github.com/dhowden/tag"
) )
@ -49,13 +49,13 @@ type plsRelation struct {
} }
func (s *ItunesScanner) ScanLibrary(lastModifiedSince time.Time, path string) (int, error) { func (s *ItunesScanner) ScanLibrary(lastModifiedSince time.Time, path string) (int, error) {
beego.Debug("Checking for updates since", lastModifiedSince.String(), "- Library:", path) log.Debug("Checking for updates", "lastModifiedSince", lastModifiedSince, "library", path)
xml, _ := os.Open(path) xml, _ := os.Open(path)
l, err := itl.ReadFromXML(xml) l, err := itl.ReadFromXML(xml)
if err != nil { if err != nil {
return 0, err return 0, err
} }
beego.Debug("Loaded", len(l.Tracks), "tracks") log.Debug("Loaded tracks", "total", len(l.Tracks))
s.lastModifiedSince = lastModifiedSince s.lastModifiedSince = lastModifiedSince
s.mediaFiles = make(map[string]*domain.MediaFile) s.mediaFiles = make(map[string]*domain.MediaFile)
@ -85,7 +85,7 @@ func (s *ItunesScanner) ScanLibrary(lastModifiedSince time.Time, path string) (i
} }
i++ i++
if i%1000 == 0 { if i%1000 == 0 {
beego.Debug("Processed", i, "tracks.", len(s.artists), "artists,", len(s.albums), "albums", len(s.mediaFiles), "songs") log.Debug(fmt.Sprintf("Processed %d tracks", i), "artists", len(s.artists), "albums", len(s.albums), "songs", len(s.mediaFiles))
} }
} }
@ -98,9 +98,9 @@ func (s *ItunesScanner) ScanLibrary(lastModifiedSince time.Time, path string) (i
} }
if err := s.checksumRepo.SetData(s.newSums); err != nil { if err := s.checksumRepo.SetData(s.newSums); err != nil {
beego.Error("Error saving checksums:", err) log.Error("Error saving checksums", err)
} else { } else {
beego.Debug("Saved", len(s.newSums), "checksums") log.Debug("Saved checksums", "total", len(s.newSums))
} }
ignFolders := conf.Sonic.PlsIgnoreFolders ignFolders := conf.Sonic.PlsIgnoreFolders
@ -116,7 +116,7 @@ func (s *ItunesScanner) ScanLibrary(lastModifiedSince time.Time, path string) (i
s.collectPlaylists(&p, fullPath) s.collectPlaylists(&p, fullPath)
} }
beego.Debug("Processed", len(l.Playlists), "playlists.") log.Debug("Processed playlists", "total", len(l.Playlists))
return len(l.Tracks), nil return len(l.Tracks), nil
} }
@ -346,26 +346,26 @@ func artistId(t *itl.Track) string {
func hasCoverArt(path string) bool { func hasCoverArt(path string) bool {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
beego.Error("Reading tag for", path, "Panic:", r) log.Error("Panic reading tag", "path", path, "error", r)
} }
}() }()
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
beego.Warn("Error opening file", path, "-", err) log.Warn("Error opening file", "path", path, err)
return false return false
} }
defer f.Close() defer f.Close()
m, err := tag.ReadFrom(f) m, err := tag.ReadFrom(f)
if err != nil { if err != nil {
beego.Warn("Error reading tag from file", path, "-", err) log.Warn("Error reading tag from file", "path", path, err)
return false return false
} }
return m.Picture() != nil return m.Picture() != nil
} }
//beego.Warn("File not found:", path) //log.Warn("File not found", "path", path)
return false return false
} }

View File

@ -4,15 +4,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"testing" "testing"
"github.com/astaxie/beego"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
) )
var initSync sync.Once
func Init(t *testing.T, skipOnShort bool) { func Init(t *testing.T, skipOnShort bool) {
if skipOnShort && testing.Short() { if skipOnShort && testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
@ -21,14 +18,11 @@ func Init(t *testing.T, skipOnShort bool) {
appPath, _ := filepath.Abs(filepath.Join(filepath.Dir(file), "..")) appPath, _ := filepath.Abs(filepath.Join(filepath.Dir(file), ".."))
confPath, _ := filepath.Abs(filepath.Join(appPath, "tests", "sonic-test.toml")) confPath, _ := filepath.Abs(filepath.Join(appPath, "tests", "sonic-test.toml"))
os.Chdir(appPath)
conf.LoadFromFile(confPath) conf.LoadFromFile(confPath)
initSync.Do(func() {
beego.TestBeegoInit(appPath)
})
noLog := os.Getenv("NOLOG") noLog := os.Getenv("NOLOG")
if noLog != "" { if noLog != "" {
beego.SetLevel(beego.LevelError) log.SetLevel(log.LevelError)
} }
} }