diff --git a/api/base_api_controller.go b/api/base_api_controller.go index bb01316dc..4f462f1d4 100644 --- a/api/base_api_controller.go +++ b/api/base_api_controller.go @@ -3,10 +3,11 @@ package api import ( "encoding/xml" "fmt" + "time" + "github.com/astaxie/beego" "github.com/deluan/gosonic/api/responses" "github.com/deluan/gosonic/utils" - "time" ) type BaseAPIController struct{ beego.Controller } @@ -23,14 +24,18 @@ func (c *BaseAPIController) RequiredParamString(param string, msg string) string return p } +func (c *BaseAPIController) ParamString(param string) string { + return c.Input().Get(param) +} + func (c *BaseAPIController) ParamTime(param string) time.Time { var value int64 c.Ctx.Input.Bind(&value, param) return utils.ToTime(value) } -func (c *BaseAPIController) ParamInt(param string) int { - var value int +func (c *BaseAPIController) ParamInt(param string, def int) int { + value := def c.Ctx.Input.Bind(&value, param) return value } diff --git a/api/get_album_list.go b/api/get_album_list.go index 7e419086a..373490f74 100644 --- a/api/get_album_list.go +++ b/api/get_album_list.go @@ -40,8 +40,8 @@ func (c *GetAlbumListController) Get() { c.SendError(responses.ERROR_GENERIC, "Not implemented!") } - offset := c.ParamInt("offset") - size := utils.MinInt(c.ParamInt("size"), 500) + offset := c.ParamInt("offset", 0) + size := utils.MinInt(c.ParamInt("size", 0), 500) albums, err := method(offset, size) if err != nil { diff --git a/api/get_cover_art.go b/api/get_cover_art.go index a17c1f605..436fb4031 100644 --- a/api/get_cover_art.go +++ b/api/get_cover_art.go @@ -19,7 +19,7 @@ func (c *GetCoverArtController) Prepare() { func (c *GetCoverArtController) Get() { id := c.RequiredParamString("id", "id parameter required") - size := c.ParamInt("size") + size := c.ParamInt("size", 0) err := c.cover.Get(id, size, c.Ctx.ResponseWriter) diff --git a/api/responses/responses.go b/api/responses/responses.go index e751470b3..d8173027e 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -6,18 +6,19 @@ import ( ) type Subsonic struct { - XMLName xml.Name `xml:"http://subsonic.org/restapi subsonic-response" json:"-"` - Status string `xml:"status,attr" json:"status"` - Version string `xml:"version,attr" json:"version"` - Error *Error `xml:"error,omitempty" json:"error,omitempty"` - License *License `xml:"license,omitempty" json:"license,omitempty"` - MusicFolders *MusicFolders `xml:"musicFolders,omitempty" json:"musicFolders,omitempty"` - Indexes *Indexes `xml:"indexes,omitempty" json:"indexes,omitempty"` - Directory *Directory `xml:"directory,omitempty" json:"directory,omitempty"` - User *User `xml:"user,omitempty" json:"user,omitempty"` - AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` - Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` - Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"` + XMLName xml.Name `xml:"http://subsonic.org/restapi subsonic-response" json:"-"` + Status string `xml:"status,attr" json:"status"` + Version string `xml:"version,attr" json:"version"` + Error *Error `xml:"error,omitempty" json:"error,omitempty"` + License *License `xml:"license,omitempty" json:"license,omitempty"` + MusicFolders *MusicFolders `xml:"musicFolders,omitempty" json:"musicFolders,omitempty"` + Indexes *Indexes `xml:"indexes,omitempty" json:"indexes,omitempty"` + Directory *Directory `xml:"directory,omitempty" json:"directory,omitempty"` + User *User `xml:"user,omitempty" json:"user,omitempty"` + AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` + Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` + Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"` + SearchResult2 *SearchResult2 `xml:"searchResult2,omitempty" json:"searchResult2,omitempty"` } type JsonWrapper struct { @@ -119,6 +120,12 @@ type PlaylistWithSongs struct { Entry []Child `xml:"entry" json:"entry,omitempty"` } +type SearchResult2 struct { + Artist []Artist `xml:"artist" json:"artist,omitempty"` + Album []Child `xml:"album" json:"album,omitempty"` + Song []Child `xml:"song" json:"song,omitempty"` +} + type User struct { Username string `xml:"username,attr" json:"username"` Email string `xml:"email,attr,omitempty" json:"email,omitempty"` diff --git a/api/search.go b/api/search.go new file mode 100644 index 000000000..9efbf975d --- /dev/null +++ b/api/search.go @@ -0,0 +1,43 @@ +package api + +import ( + "github.com/astaxie/beego" + "github.com/deluan/gosonic/api/responses" + "github.com/deluan/gosonic/engine" + "github.com/deluan/gosonic/utils" + "github.com/karlkfi/inject" +) + +type SearchingController struct { + BaseAPIController + search engine.Search +} + +func (c *SearchingController) Prepare() { + inject.ExtractAssignable(utils.Graph, &c.search) +} + +func (c *SearchingController) Search2() { + query := c.RequiredParamString("query", "Parameter query required") + artistCount := c.ParamInt("artistCount", 20) + artistOffset := c.ParamInt("artistOffset", 0) + //albumCount := c.ParamInt("albumCount", 20) + //albumOffset := c.ParamInt("albumOffset", 0) + //songCount := c.ParamInt("songCount", 20) + //songOffset := c.ParamInt("songOffset", 0) + + as, err := c.search.SearchArtist(query, artistOffset, artistCount) + if err != nil { + beego.Error("Error searching for Artists:", err) + c.SendError(responses.ERROR_GENERIC, "Internal Error") + } + + response := c.NewEmpty() + searchResult2 := &responses.SearchResult2{} + searchResult2.Artist = make([]responses.Artist, len(*as)) + for i, a := range *as { + searchResult2.Artist[i] = responses.Artist{Id: a.Id, Name: a.Name} + } + response.SearchResult2 = searchResult2 + c.SendResponse(response) +} diff --git a/api/stream.go b/api/stream.go index 854fcb066..60c64cb3c 100644 --- a/api/stream.go +++ b/api/stream.go @@ -38,7 +38,7 @@ func (c *StreamController) Prepare() { // TODO Still getting the "Conn.Write wrote more than the declared Content-Length" error. // Don't know if this causes any issues func (c *StreamController) Stream() { - maxBitRate := c.ParamInt("maxBitRate") + maxBitRate := c.ParamInt("maxBitRate", 0) maxBitRate = utils.MinInt(c.mf.BitRate, maxBitRate) beego.Debug("Streaming file", c.id, ":", c.mf.Path) diff --git a/conf/inject_definitions.go b/conf/inject_definitions.go index e899b33ed..32e115b79 100644 --- a/conf/inject_definitions.go +++ b/conf/inject_definitions.go @@ -29,7 +29,13 @@ func init() { // Other dependencies utils.DefineSingleton(new(scanner.Scanner), scanner.NewItunesScanner) - utils.DefineSingleton(new(gomate.Indexer), func() gomate.Indexer { - return gomate.NewIndexer(gomate.NewLedisEmbeddedDB(persistence.Db())) + utils.DefineSingleton(new(gomate.DB), func() gomate.DB { + return gomate.NewLedisEmbeddedDB(persistence.Db()) }) + //utils.DefineSingleton(new(gomate.Indexer), func() gomate.Indexer { + // return gomate.NewIndexer(gomate.NewLedisEmbeddedDB(persistence.Db())) + //}) + //utils.DefineSingleton(new(gomate.Searcher), func() gomate.Searcher { + // return gomate.NewSearcher(gomate.NewLedisEmbeddedDB(persistence.Db())) + //}) } diff --git a/conf/router.go b/conf/router.go index c821d5e0f..0182c0617 100644 --- a/conf/router.go +++ b/conf/router.go @@ -23,6 +23,8 @@ func mapEndpoints() { beego.NSRouter("/getIndexes.view", &api.BrowsingController{}, "*:GetIndexes"), beego.NSRouter("/getMusicDirectory.view", &api.BrowsingController{}, "*:GetDirectory"), + beego.NSRouter("/search2.view", &api.SearchingController{}, "*:Search2"), + beego.NSRouter("/getCoverArt.view", &api.GetCoverArtController{}, "*:Get"), beego.NSRouter("/stream.view", &api.StreamController{}, "*:Stream"), beego.NSRouter("/download.view", &api.StreamController{}, "*:Download"), diff --git a/engine/search.go b/engine/search.go index 19b2d1d0d..b24d4a039 100644 --- a/engine/search.go +++ b/engine/search.go @@ -12,31 +12,76 @@ type Search interface { IndexArtist(ar *domain.Artist) error IndexAlbum(al *domain.Album) error IndexMediaFile(mf *domain.MediaFile) error + + SearchArtist(q string, offset int, size int) (*domain.Artists, error) + //SearchAlbum(q string, offset int, size int) (*domain.Albums, error) + //SearchSong(q string, offset int, size int) (*domain.MediaFiles, error) } type search struct { artistRepo domain.ArtistRepository albumRepo domain.AlbumRepository mfileRepo domain.MediaFileRepository - indexer gomate.Indexer + idxArtist gomate.Indexer + idxAlbum gomate.Indexer + idxSong gomate.Indexer + sArtist gomate.Searcher + sAlbum gomate.Searcher + sSong gomate.Searcher } -func NewSearch(ar domain.ArtistRepository, alr domain.AlbumRepository, mr domain.MediaFileRepository, idx gomate.Indexer) Search { - return search{ar, alr, mr, idx} +func NewSearch(ar domain.ArtistRepository, alr domain.AlbumRepository, mr domain.MediaFileRepository, db gomate.DB) Search { + s := search{artistRepo: ar, albumRepo: alr, mfileRepo: mr} + s.idxArtist = gomate.NewIndexer(db, "gomate-artist-idx") + s.sArtist = gomate.NewSearcher(db, "gomate-artist-idx") + s.idxAlbum = gomate.NewIndexer(db, "gomate-album-idx") + s.sAlbum = gomate.NewSearcher(db, "gomate-album-idx") + s.idxSong = gomate.NewIndexer(db, "gomate-song-idx") + s.sSong = gomate.NewSearcher(db, "gomate-song-idx") + return s } func (s search) ClearAll() error { - return s.indexer.Clear() + return s.idxArtist.Clear() + return s.idxAlbum.Clear() + return s.idxSong.Clear() } func (s search) IndexArtist(ar *domain.Artist) error { - return s.indexer.Index("ar-"+ar.Id, strings.ToLower(ar.Name)) + return s.idxArtist.Index(ar.Id, strings.ToLower(ar.Name)) } func (s search) IndexAlbum(al *domain.Album) error { - return s.indexer.Index("al-"+al.Id, strings.ToLower(al.Name)) + return s.idxAlbum.Index(al.Id, strings.ToLower(al.Name)) } func (s search) IndexMediaFile(mf *domain.MediaFile) error { - return s.indexer.Index("mf-"+mf.Id, strings.ToLower(mf.Title)) + return s.idxSong.Index(mf.Id, strings.ToLower(mf.Title)) } + +func (s search) SearchArtist(q string, offset int, size int) (*domain.Artists, error) { + q = strings.TrimSuffix(q, "*") + res, err := s.sArtist.Search(q) + if err != nil { + return nil, nil + } + as := make(domain.Artists, 0, len(res)) + for _, id := range res { + a, err := s.artistRepo.Get(id) + if err != nil { + return nil, err + } + as = append(as, *a) + } + return &as, nil +} + +//func (s search) SearchAlbum(q string, offset int, size int) (*domain.Albums, error) { +// q := strings.TrimSuffix(q, "*") +// return nil +//} +// +//func (s search) SearchSong(q string, offset int, size int) (*domain.MediaFiles, error) { +// q := strings.TrimSuffix(q, "*") +// return nil +//}