diff --git a/server/handler/construct_sub_by_folder.go b/server/handler/construct_sub_by_folder.go index 59b7f9a..ea3792a 100644 --- a/server/handler/construct_sub_by_folder.go +++ b/server/handler/construct_sub_by_folder.go @@ -6,13 +6,16 @@ import ( ) func makeChildFromFolder(f *model.Folder, parent *model.Folder) *subsonic.Child { - return &subsonic.Child{ - ID: f.ID, - Title: f.Name, - CoverID: f.CoverID, - ParentID: parent.ID, - IsDir: true, + child := &subsonic.Child{ + ID: f.ID, + Title: f.Name, + CoverID: f.CoverID, + IsDir: true, } + if parent != nil { + child.ParentID = parent.ID + } + return child } func makeChildFromTrack(t *model.Track, parent *model.Folder) *subsonic.Child { diff --git a/server/handler/handler_sub_by_folder.go b/server/handler/handler_sub_by_folder.go index 65ee1d1..0ca2aaa 100644 --- a/server/handler/handler_sub_by_folder.go +++ b/server/handler/handler_sub_by_folder.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" "github.com/jinzhu/gorm" @@ -9,11 +10,15 @@ import ( "github.com/sentriz/gonic/server/subsonic" ) +// the subsonic spec metions "artist" a lot when talking about the +// browse by folder endpoints. but since we're not browsing by tag +// we can't access artists. so instead we'll consider the artist of +// an track to be the it's respective folder that comes directly +// under the root directory + func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) { - // we are browsing by folder, but the subsonic docs show sub elements - // for this, so we're going to return root directories as "artists" var folders []model.Folder - c.DB.Where("parent_id = ?", 1).Find(&folders) + c.DB.Where("parent_id = 1").Find(&folders) var indexMap = make(map[rune]*subsonic.Index) var indexes []*subsonic.Index for _, folder := range folders { @@ -97,14 +102,11 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) { q := c.DB switch listType { case "alphabeticalByArtist": - // not sure what it meant by "artist" since we're browsing by folder - // - so we'll consider the parent folder's name to be the "artist" q = q.Joins(` JOIN folders AS parent_folders ON folders.parent_id = parent_folders.id`) q = q.Order("parent_folders.name") case "alphabeticalByName": - // not sure about "name" either, so lets use the folder's name q = q.Order("name") case "frequent": user := r.Context().Value(contextUserKey).(*model.User) @@ -144,3 +146,55 @@ func (c *Controller) GetAlbumList(w http.ResponseWriter, r *http.Request) { } respond(w, r, sub) } + +func (c *Controller) SearchTwo(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%%", query) + results := &subsonic.SearchResultTwo{} + // + // search "artists" + var artists []model.Folder + c.DB. + Where("parent_id = 1 AND name LIKE ?", query). + Offset(getIntParamOr(r, "artistOffset", 0)). + Limit(getIntParamOr(r, "artistCount", 20)). + Find(&artists) + for _, a := range artists { + results.Artists = append(results.Artists, + makeChildFromFolder(&a, nil)) + } + // + // search "albums" + var albums []model.Folder + c.DB. + Preload("Parent"). + Where("has_tracks = 1 AND name LIKE ?", query). + Offset(getIntParamOr(r, "albumOffset", 0)). + Limit(getIntParamOr(r, "albumCount", 20)). + Find(&albums) + for _, a := range albums { + results.Albums = append(results.Albums, + makeChildFromFolder(&a, a.Parent)) + } + // + // search "artists" + var tracks []model.Track + c.DB. + Preload("Folder"). + 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, + makeChildFromTrack(&t, &t.Folder)) + } + // + sub := subsonic.NewResponse() + sub.SearchResultTwo = results + respond(w, r, sub) +} diff --git a/server/setup_subsonic.go b/server/setup_subsonic.go index 25cd386..6344d16 100644 --- a/server/setup_subsonic.go +++ b/server/setup_subsonic.go @@ -41,4 +41,6 @@ func (s *Server) setupSubsonic() { s.mux.HandleFunc("/rest/getMusicDirectory.view", withWare(s.GetMusicDirectory)) s.mux.HandleFunc("/rest/getAlbumList", withWare(s.GetAlbumList)) s.mux.HandleFunc("/rest/getAlbumList.view", withWare(s.GetAlbumList)) + s.mux.HandleFunc("/rest/search2", withWare(s.SearchTwo)) + s.mux.HandleFunc("/rest/search2.view", withWare(s.SearchTwo)) } diff --git a/server/subsonic/media.go b/server/subsonic/media.go index fde1bd5..e4394ee 100644 --- a/server/subsonic/media.go +++ b/server/subsonic/media.go @@ -32,26 +32,26 @@ type RandomTracks struct { } type Track struct { - ID int `xml:"id,attr,omitempty" json:"id"` - Parent int `xml:"parent,attr,omitempty" json:"parent"` - Title string `xml:"title,attr,omitempty" json:"title"` - Album string `xml:"album,attr,omitempty" json:"album"` - Artist string `xml:"artist,attr,omitempty" json:"artist"` - IsDir bool `xml:"isDir,attr,omitempty" json:"isDir"` - 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"` - Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate"` - Size int `xml:"size,attr,omitempty" json:"size"` - Suffix string `xml:"suffix,attr,omitempty" json:"suffix"` - ContentType string `xml:"contentType,attr,omitempty" json:"contentType"` - IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo"` - Path string `xml:"path,attr,omitempty" json:"path"` - AlbumID int `xml:"albumId,attr,omitempty" json:"albumId"` - ArtistID int `xml:"artistId,attr,omitempty" json:"artistId"` - TrackNumber int `xml:"track,attr,omitempty" json:"track"` - Type string `xml:"type,attr,omitempty" json:"type"` + ID int `xml:"id,attr,omitempty" json:"id"` + Parent int `xml:"parent,attr,omitempty" json:"parent"` + Title string `xml:"title,attr,omitempty" json:"title"` + Album string `xml:"album,attr,omitempty" json:"album"` + Artist string `xml:"artist,attr,omitempty" json:"artist"` + IsDir bool `xml:"isDir,attr,omitempty" json:"isDir"` + 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"` + Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate"` + Size int `xml:"size,attr,omitempty" json:"size"` + Suffix string `xml:"suffix,attr,omitempty" json:"suffix"` + ContentType string `xml:"contentType,attr,omitempty" json:"contentType"` + IsVideo bool `xml:"isVideo,attr,omitempty" json:"isVideo"` + Path string `xml:"path,attr,omitempty" json:"path"` + AlbumID int `xml:"albumId,attr,omitempty" json:"albumId"` + ArtistID int `xml:"artistId,attr,omitempty" json:"artistId"` + TrackNumber int `xml:"track,attr,omitempty" json:"track"` + Type string `xml:"type,attr,omitempty" json:"type"` } type Artists struct { @@ -123,3 +123,9 @@ type ScanStatus struct { Scanning bool `xml:"scanning,attr" json:"scanning"` Count int `xml:"count,attr,omitempty" json:"count,omitempty"` } + +type SearchResultTwo struct { + Artists []*Child `xml:"artist" json:"artist"` + Albums []*Child `xml:"album" json:"album"` + Tracks []*Child `xml:"song" json:"song"` +} diff --git a/server/subsonic/response.go b/server/subsonic/response.go index 1b1968c..702ac47 100644 --- a/server/subsonic/response.go +++ b/server/subsonic/response.go @@ -15,22 +15,23 @@ type MetaResponse struct { } 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"` + 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 {