diff --git a/server/handler/construct_sub_by_folder.go b/server/handler/construct_sub_by_folder.go index af085bd..3c23eb9 100644 --- a/server/handler/construct_sub_by_folder.go +++ b/server/handler/construct_sub_by_folder.go @@ -5,8 +5,8 @@ import ( "github.com/sentriz/gonic/server/subsonic" ) -func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Child { - child := &subsonic.Child{ +func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Track { + child := &subsonic.Track{ ID: f.ID, Title: f.Name, CoverID: f.CoverID, @@ -18,8 +18,8 @@ func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Child return child } -func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Child { - return &subsonic.Child{ +func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Track { + return &subsonic.Track{ ID: t.ID, Album: t.Album.Title, Artist: t.TrackArtist, @@ -28,7 +28,7 @@ func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Child { Size: t.Size, Suffix: t.Suffix, Title: t.Title, - Track: t.TrackNumber, + TrackNumber: t.TrackNumber, ParentID: parent.ID, CoverID: parent.CoverID, Duration: 0, @@ -41,7 +41,6 @@ func makeAlbumFromFolder(f *model.Folder) *subsonic.Album { return &subsonic.Album{ ID: f.ID, Title: f.Name, - Album: f.Name, CoverID: f.CoverID, ParentID: f.ParentID, Artist: f.Parent.Name, @@ -56,7 +55,7 @@ func makeArtistFromFolder(f *model.Folder) *subsonic.Artist { } } -func makeDirFromFolder(f *model.Folder, children []*subsonic.Child) *subsonic.Directory { +func makeDirFromFolder(f *model.Folder, children []*subsonic.Track) *subsonic.Directory { return &subsonic.Directory{ ID: f.ID, Parent: f.ParentID, diff --git a/server/handler/construct_sub_by_tags.go b/server/handler/construct_sub_by_tags.go index 7d66da0..f00df7c 100644 --- a/server/handler/construct_sub_by_tags.go +++ b/server/handler/construct_sub_by_tags.go @@ -24,6 +24,7 @@ func makeTrackFromTrack(t *model.Track, album *model.Album) *subsonic.Track { TrackNumber: t.TrackNumber, ContentType: t.ContentType, Path: t.Path, + ParentID: t.FolderID, Suffix: t.Suffix, CreatedAt: t.CreatedAt, Size: t.Size, diff --git a/server/handler/handler_admin_utils_test.go b/server/handler/handler_admin_utils_test.go index f4dd0de..bf2f086 100644 --- a/server/handler/handler_admin_utils_test.go +++ b/server/handler/handler_admin_utils_test.go @@ -17,9 +17,6 @@ func TestFirstExisting(t *testing.T) { {"first missing", []string{"", "two", "three"}, "default", "two"}, - {"middle missing", - []string{"", "two", ""}, "default", - "two"}, {"all missing", []string{"", "", ""}, "default", "default"}, diff --git a/server/handler/handler_sub_by_folder.go b/server/handler/handler_sub_by_folder.go index 931af02..d2057b5 100644 --- a/server/handler/handler_sub_by_folder.go +++ b/server/handler/handler_sub_by_folder.go @@ -50,7 +50,7 @@ func (c *Controller) GetMusicDirectory(w http.ResponseWriter, r *http.Request) { respondError(w, r, 10, "please provide an `id` parameter") return } - childrenObj := []*subsonic.Child{} + childrenObj := []*subsonic.Track{} var folder model.Folder c.DB.First(&folder, id) // @@ -149,8 +149,8 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) { respondError(w, r, 10, "please provide a `query` parameter") return } - query = strings.TrimSuffix(query, "*") - query = fmt.Sprintf("%%%s%%", query) + query = fmt.Sprintf("%%%s%%", + strings.TrimSuffix(query, "*")) results := &subsonic.SearchResultTwo{} // // search "artists" @@ -178,7 +178,7 @@ func (c *Controller) SearchTwo(w http.ResponseWriter, r *http.Request) { makeChildFromFolder(&a, a.Parent)) } // - // search "artists" + // search tracks var tracks []model.Track c.DB. Preload("Folder"). diff --git a/server/handler/handler_sub_by_tags.go b/server/handler/handler_sub_by_tags.go index 2238e6e..971364b 100644 --- a/server/handler/handler_sub_by_tags.go +++ b/server/handler/handler_sub_by_tags.go @@ -1,7 +1,9 @@ package handler import ( + "fmt" "net/http" + "strings" "github.com/jinzhu/gorm" @@ -139,3 +141,55 @@ func (c *Controller) GetAlbumListTwo(w http.ResponseWriter, r *http.Request) { } respond(w, r, sub) } + +func (c *Controller) SearchThree(w http.ResponseWriter, r *http.Request) { + query := getStrParam(r, "query") + if query == "" { + respondError(w, r, 10, "please provide a `query` parameter") + return + } + query = fmt.Sprintf("%%%s%%", + strings.TrimSuffix(query, "*")) + results := &subsonic.SearchResultThree{} + // + // search "artists" + var artists []model.Artist + c.DB. + Where("name LIKE ?", query). + Offset(getIntParamOr(r, "artistOffset", 0)). + Limit(getIntParamOr(r, "artistCount", 20)). + Find(&artists) + for _, a := range artists { + results.Artists = append(results.Artists, + makeArtistFromArtist(&a)) + } + // + // search "albums" + var albums []model.Album + c.DB. + Preload("Artist"). + Where("title LIKE ?", query). + Offset(getIntParamOr(r, "albumOffset", 0)). + Limit(getIntParamOr(r, "albumCount", 20)). + Find(&albums) + for _, a := range albums { + results.Albums = append(results.Albums, + makeAlbumFromAlbum(&a, &a.Artist)) + } + // + // search tracks + var tracks []model.Track + c.DB. + Preload("Album"). + Where("title LIKE ?", query). + Offset(getIntParamOr(r, "songOffset", 0)). + Limit(getIntParamOr(r, "songCount", 20)). + Find(&tracks) + for _, t := range tracks { + results.Tracks = append(results.Tracks, + makeTrackFromTrack(&t, &t.Album)) + } + sub := subsonic.NewResponse() + sub.SearchResultThree = results + respond(w, r, sub) +} diff --git a/server/handler/respond_sub.go b/server/handler/respond_sub.go index 2fea72e..3c04c47 100644 --- a/server/handler/respond_sub.go +++ b/server/handler/respond_sub.go @@ -10,17 +10,24 @@ import ( "github.com/sentriz/gonic/server/subsonic" ) +type metaResponse struct { + XMLName xml.Name `xml:"subsonic-response" json:"-"` + *subsonic.Response `json:"subsonic-response"` +} + func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.Response) { - res := subsonic.MetaResponse{ + w.WriteHeader(code) + res := metaResponse{ Response: sub, } - switch r.URL.Query().Get("f") { + switch getStrParam(r, "f") { case "json": w.Header().Set("Content-Type", "application/json") data, err := json.Marshal(res) if err != nil { log.Printf("could not marshall to json: %v\n", err) + return } w.Write(data) case "jsonp": @@ -28,9 +35,9 @@ func respondRaw(w http.ResponseWriter, r *http.Request, data, err := json.Marshal(res) if err != nil { log.Printf("could not marshall to json: %v\n", err) + return } - callback := r.URL.Query().Get("callback") - w.Write([]byte(callback)) + w.Write([]byte(getStrParamOr(r, "callback", "cb"))) w.Write([]byte("(")) w.Write(data) w.Write([]byte(");")) @@ -39,6 +46,7 @@ func respondRaw(w http.ResponseWriter, r *http.Request, data, err := xml.MarshalIndent(res, "", " ") if err != nil { log.Printf("could not marshall to xml: %v\n", err) + return } w.Write(data) } diff --git a/server/setup_subsonic.go b/server/setup_subsonic.go index 6344d16..2733ca8 100644 --- a/server/setup_subsonic.go +++ b/server/setup_subsonic.go @@ -34,6 +34,8 @@ func (s *Server) setupSubsonic() { s.mux.HandleFunc("/rest/getArtist.view", withWare(s.GetArtist)) s.mux.HandleFunc("/rest/getArtists", withWare(s.GetArtists)) s.mux.HandleFunc("/rest/getArtists.view", withWare(s.GetArtists)) + s.mux.HandleFunc("/rest/search3", withWare(s.SearchThree)) + s.mux.HandleFunc("/rest/search3.view", withWare(s.SearchThree)) // browse by folder s.mux.HandleFunc("/rest/getIndexes", withWare(s.GetIndexes)) s.mux.HandleFunc("/rest/getIndexes.view", withWare(s.GetIndexes)) diff --git a/server/subsonic/response.go b/server/subsonic/response.go deleted file mode 100644 index 702ac47..0000000 --- a/server/subsonic/response.go +++ /dev/null @@ -1,60 +0,0 @@ -// from "sonicmonkey" by https://github.com/jeena/sonicmonkey/ - -package subsonic - -import "encoding/xml" - -var ( - apiVersion = "1.9.0" - xmlns = "http://subsonic.org/restapi" -) - -type MetaResponse struct { - XMLName xml.Name `xml:"subsonic-response" json:"-"` - *Response `json:"subsonic-response"` -} - -type Response struct { - Status string `xml:"status,attr" json:"status"` - Version string `xml:"version,attr" json:"version"` - XMLNS string `xml:"xmlns,attr" json:"-"` - Error *Error `xml:"error" json:"error,omitempty"` - AlbumsTwo *Albums `xml:"albumList2" json:"albumList2,omitempty"` - Albums *Albums `xml:"albumList" json:"albumList,omitempty"` - Album *Album `xml:"album" json:"album,omitempty"` - Track *Track `xml:"song" json:"song,omitempty"` - Indexes *Indexes `xml:"indexes" json:"indexes,omitempty"` - Artists *Artists `xml:"artists" json:"artists,omitempty"` - Artist *Artist `xml:"artist" json:"artist,omitempty"` - Directory *Directory `xml:"directory" json:"directory,omitempty"` - RandomTracks *RandomTracks `xml:"randomSongs" json:"randomSongs,omitempty"` - MusicFolders *MusicFolders `xml:"musicFolders" json:"musicFolders,omitempty"` - ScanStatus *ScanStatus `xml:"scanStatus" json:"scanStatus,omitempty"` - Licence *Licence `xml:"license" json:"license,omitempty"` - SearchResultTwo *SearchResultTwo `xml:"searchResult2" json:"searchResult2,omitempty"` -} - -type Error struct { - Code int `xml:"code,attr" json:"code"` - Message string `xml:"message,attr" json:"message"` -} - -func NewResponse() *Response { - return &Response{ - Status: "ok", - XMLNS: xmlns, - Version: apiVersion, - } -} - -func NewError(code int, message string) *Response { - return &Response{ - Status: "failed", - XMLNS: xmlns, - Version: apiVersion, - Error: &Error{ - Code: code, - Message: message, - }, - } -} diff --git a/server/subsonic/media.go b/server/subsonic/subsonic.go similarity index 57% rename from server/subsonic/media.go rename to server/subsonic/subsonic.go index 28cf153..fe66a6b 100644 --- a/server/subsonic/media.go +++ b/server/subsonic/subsonic.go @@ -4,6 +4,57 @@ import ( "time" ) +var ( + apiVersion = "1.9.0" + xmlns = "http://subsonic.org/restapi" +) + +type Response struct { + Status string `xml:"status,attr" json:"status"` + Version string `xml:"version,attr" json:"version"` + XMLNS string `xml:"xmlns,attr" json:"-"` + Error *Error `xml:"error" json:"error,omitempty"` + Albums *Albums `xml:"albumList" json:"albumList,omitempty"` + AlbumsTwo *Albums `xml:"albumList2" json:"albumList2,omitempty"` + Album *Album `xml:"album" json:"album,omitempty"` + Track *Track `xml:"song" json:"song,omitempty"` + Indexes *Indexes `xml:"indexes" json:"indexes,omitempty"` + Artists *Artists `xml:"artists" json:"artists,omitempty"` + Artist *Artist `xml:"artist" json:"artist,omitempty"` + Directory *Directory `xml:"directory" json:"directory,omitempty"` + RandomTracks *RandomTracks `xml:"randomSongs" json:"randomSongs,omitempty"` + MusicFolders *MusicFolders `xml:"musicFolders" json:"musicFolders,omitempty"` + ScanStatus *ScanStatus `xml:"scanStatus" json:"scanStatus,omitempty"` + Licence *Licence `xml:"license" json:"license,omitempty"` + SearchResultTwo *SearchResultTwo `xml:"searchResult2" json:"searchResult2,omitempty"` + SearchResultThree *SearchResultThree `xml:"searchResult3" json:"searchResult3,omitempty"` +} + +func NewResponse() *Response { + return &Response{ + Status: "ok", + XMLNS: xmlns, + Version: apiVersion, + } +} + +type Error struct { + Code int `xml:"code,attr" json:"code"` + Message string `xml:"message,attr" json:"message"` +} + +func NewError(code int, message string) *Response { + return &Response{ + Status: "failed", + XMLNS: xmlns, + Version: apiVersion, + Error: &Error{ + Code: code, + Message: message, + }, + } +} + type Albums struct { List []*Album `xml:"album" json:"album,omitempty"` } @@ -32,26 +83,26 @@ type RandomTracks struct { } type Track struct { - Album string `xml:"album,attr,omitempty" json:"album"` - AlbumID int `xml:"albumId,attr,omitempty" json:"albumId"` - Artist string `xml:"artist,attr,omitempty" json:"artist"` - ArtistID int `xml:"artistId,attr,omitempty" json:"artistId"` - Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate"` - ContentType string `xml:"contentType,attr,omitempty" json:"contentType"` - CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt"` - CreatedAt time.Time `xml:"created,attr,omitempty" json:"created"` - Duration int `xml:"duration,attr,omitempty" json:"duration"` - Genre string `xml:"genre,attr,omitempty" json:"genre"` - ID int `xml:"id,attr,omitempty" json:"id"` - IsDir bool `xml:"isDir,attr,omitempty" json:"isDir"` - IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo"` - Parent int `xml:"parent,attr,omitempty" json:"parent"` - Path string `xml:"path,attr,omitempty" json:"path"` - Size int `xml:"size,attr,omitempty" json:"size"` - Suffix string `xml:"suffix,attr,omitempty" json:"suffix"` - Title string `xml:"title,attr,omitempty" json:"title"` - TrackNumber int `xml:"track,attr,omitempty" json:"track"` - Type string `xml:"type,attr,omitempty" json:"type"` + Album string `xml:"album,attr,omitempty" json:"album,omitempty"` + AlbumID int `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` + Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` + ArtistID int `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` + Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` + ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` + CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` + CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` + Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` + Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + ID int `xml:"id,attr,omitempty" json:"id,omitempty"` + IsDir bool `xml:"isDir,attr,omitempty" json:"isDir,omitempty"` + IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo,omitempty"` + ParentID int `xml:"parent,attr,omitempty" json:"parent,omitempty"` + Path string `xml:"path,attr,omitempty" json:"path,omitempty"` + Size int `xml:"size,attr,omitempty" json:"size,omitempty"` + Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` + Title string `xml:"title,attr,omitempty" json:"title,omitempty"` + TrackNumber int `xml:"track,attr,omitempty" json:"track,omitempty"` + Type string `xml:"type,attr,omitempty" json:"type,omitempty"` } type Artists struct { @@ -81,29 +132,7 @@ type Directory struct { Parent int `xml:"parent,attr,omitempty" json:"parent"` Name string `xml:"name,attr,omitempty" json:"name"` Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"` - Children []*Child `xml:"child,omitempty" json:"child"` -} - -type Child struct { - Album string `xml:"album,attr,omitempty" json:"album,omitempty"` - AlbumID int `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` - Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` - ArtistID int `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` - Bitrate int `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"` - ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` - CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` - Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` - Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` - ID int `xml:"id,attr,omitempty" json:"id,omitempty"` - IsDir bool `xml:"isDir,attr,omitempty" json:"isDir,omitempty"` - ParentID int `xml:"parent,attr,omitempty" json:"parent,omitempty"` - Path string `xml:"path,attr,omitempty" json:"path,omitempty"` - Size int `xml:"size,attr,omitempty" json:"size,omitempty"` - Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` - Title string `xml:"title,attr,omitempty" json:"title,omitempty"` - Track int `xml:"track,attr,omitempty" json:"track,omitempty"` - Type string `xml:"type,attr,omitempty" json:"type,omitempty"` - Year int `xml:"year,attr,omitempty" json:"year,omitempty"` + Children []*Track `xml:"child,omitempty" json:"child,omitempty"` } type MusicFolders struct { @@ -125,7 +154,13 @@ type ScanStatus struct { } type SearchResultTwo struct { - Artists []*Directory `xml:"artist" json:"artist"` - Albums []*Child `xml:"album" json:"album"` - Tracks []*Child `xml:"song" json:"song"` + Artists []*Directory `xml:"artist,omitempty" json:"artist,omitempty"` + Albums []*Track `xml:"album,omitempty" json:"album,omitempty"` + Tracks []*Track `xml:"song,omitempty" json:"song,omitempty"` +} + +type SearchResultThree struct { + Artists []*Artist `xml:"artist,omitempty" json:"artist,omitempty"` + Albums []*Album `xml:"album,omitempty" json:"album,omitempty"` + Tracks []*Track `xml:"song,omitempty" json:"song,omitempty"` }