diff --git a/client/mpd/djrandom.go b/client/mpd/djrandom.go index f425ede1a097234d2a830c7777d4f08f9a74751a..e43cded11264346b239374e8f7cb9f10d90589c6 100644 --- a/client/mpd/djrandom.go +++ b/client/mpd/djrandom.go @@ -10,6 +10,7 @@ import ( "log" "net/http" "net/url" + "strconv" "strings" "sync" @@ -23,6 +24,7 @@ import ( var ( configFile = flag.String("djrandom-config", util.ExpandTilde("~/.djrandom.conf"), "Config file location") + maxResults = flag.Int("max-search-results", 200, "maximum number of search results to return") serverUrl = config.String("djrandom-server", "https://djrandom.incal.net", "Server URL") authKeyId = config.String("auth_key", "", "API authentication key") @@ -182,6 +184,7 @@ func (d *DJRandomDatabase) doQuery(query string) ([]mpd.Song, error) { values := url.Values{} values.Add("q", query) + values.Add("limit", strconv.Itoa(*maxResults)) req, err := d.client.NewRequest("POST", "/api/search", strings.NewReader(values.Encode())) if err != nil { return nil, err diff --git a/server/frontend/api_views.go b/server/frontend/api_views.go index deaaabddfe67a6669815206e0f7eaad41e25ec91..3cab330e53e98284b0b7144b5e15ed74e4471f0f 100644 --- a/server/frontend/api_views.go +++ b/server/frontend/api_views.go @@ -2,6 +2,7 @@ package frontend import ( "encoding/json" + "errors" // "fmt" "io" "io/ioutil" @@ -182,49 +183,67 @@ func CheckFingerprints(w http.ResponseWriter, r *djRequest) { sendJsonResponse(w, &fpResp) } -// SearchIds runs a search query and returns only song IDs. -func SearchIds(w http.ResponseWriter, r *djRequest) { - // Assemble the search query. +func doSearch(r *djRequest) ([]api.SongID, error) { + // Extract query parameters from the request. query := r.Request.FormValue("q") if query == "" { - http.Error(w, "Empty search query", http.StatusBadRequest) - return + return nil, errors.New("empty search query") + } + var limit int + limitstr := r.Request.FormValue("limit") + if limitstr != "" { + var err error + limit, err = strconv.Atoi(limitstr) + if err != nil { + return nil, err + } } - // Run search. - var resp api.SearchIdsResponse - for item, _ := range r.Ctx.Index.Search(query) { - resp.Results = append(resp.Results, item) + // Run search, only return 'limit' results (if 0, return all + // of them). Try to use the least memory possible. + results := r.Ctx.Index.Search(query) + if limit == 0 { + limit = len(results) } - sendJsonResponse(w, &resp) + + songIds := make([]api.SongID, 0, limit) + i := 0 + for id := range results { + songIds = append(songIds, id) + i++ + if i > limit { + break + } + } + + return songIds, nil } -// Search returns full search results. -func Search(w http.ResponseWriter, r *djRequest) { - // Assemble the search query. - query := r.Request.FormValue("q") - if query == "" { - http.Error(w, "Empty search query", http.StatusBadRequest) +// SearchIds runs a search query and returns only song IDs. +func SearchIds(w http.ResponseWriter, r *djRequest) { + ids, err := doSearch(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } + sendJsonResponse(w, &api.SearchIdsResponse{Results: ids}) +} - // Run search (and create a list). - results := r.Ctx.Index.Search(query) - songIds := make([]api.SongID, 0, len(results)) - for id, _ := range results { - songIds = append(songIds, id) +// Search returns full search results. +func Search(w http.ResponseWriter, r *djRequest) { + ids, err := doSearch(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return } - songs, err := db_client.ParallelFetchSongs(r.Ctx.Db, songIds) + songs, err := db_client.ParallelFetchSongs(r.Ctx.Db, ids) if err != nil { log.Printf("Search(): %s", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := api.SearchResponse{ - Results: songs, - } - sendJsonResponse(w, &resp) + sendJsonResponse(w, &api.SearchResponse{Results: songs}) } // ArtistAutocomplete returns artist autocompletion results.