Subsonic: album/song lists (getAlbumList/2, getRandomSongs, getSongsByGenre, getNowPlaying, getStarred/2) and search (search, search2, search3)
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("search", handleSearch)
|
||||
register("search2", handleSearch2)
|
||||
register("search3", handleSearch3)
|
||||
}
|
||||
|
||||
type searchMatches struct {
|
||||
Artists []*database.Artist
|
||||
Albums []*database.Album
|
||||
Songs []*database.Song
|
||||
}
|
||||
|
||||
// runSearch performs a simple substring, case-insensitive match against
|
||||
// artist/album/song names, with independent offset/count for each
|
||||
// category as the protocol requires. VibeSonic's libraries are small
|
||||
// enough (self-hosted, not a streaming catalog) that a LIKE scan is
|
||||
// plenty fast; there's no need for SQLite FTS's added complexity here.
|
||||
func runSearch(db *sql.DB, query string, artistOffset, artistCount, albumOffset, albumCount, songOffset, songCount int) (searchMatches, error) {
|
||||
var m searchMatches
|
||||
if query == "" {
|
||||
return m, nil
|
||||
}
|
||||
like := "%" + query + "%"
|
||||
|
||||
artistRows, err := db.Query(`SELECT DISTINCT a.id FROM artists a JOIN album_artists aa ON aa.artist_id = a.id
|
||||
WHERE a.name LIKE ? COLLATE NOCASE ORDER BY a.name COLLATE NOCASE LIMIT ? OFFSET ?`, like, artistCount, artistOffset)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
for artistRows.Next() {
|
||||
var id string
|
||||
if err := artistRows.Scan(&id); err == nil {
|
||||
if a, err := database.GetArtist(db, id); err == nil {
|
||||
m.Artists = append(m.Artists, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
artistRows.Close()
|
||||
|
||||
albumRows, err := db.Query(`SELECT id FROM albums WHERE title LIKE ? COLLATE NOCASE ORDER BY title COLLATE NOCASE LIMIT ? OFFSET ?`, like, albumCount, albumOffset)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
for albumRows.Next() {
|
||||
var id string
|
||||
if err := albumRows.Scan(&id); err == nil {
|
||||
if a, err := database.GetAlbum(db, id); err == nil {
|
||||
m.Albums = append(m.Albums, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
albumRows.Close()
|
||||
|
||||
songRows, err := db.Query(`SELECT id FROM songs WHERE title LIKE ? COLLATE NOCASE ORDER BY title COLLATE NOCASE LIMIT ? OFFSET ?`, like, songCount, songOffset)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
for songRows.Next() {
|
||||
var id string
|
||||
if err := songRows.Scan(&id); err == nil {
|
||||
if s, err := database.GetSong(db, id); err == nil {
|
||||
m.Songs = append(m.Songs, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
songRows.Close()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// handleSearch implements the original, deprecated (since 1.4.0, but
|
||||
// still required) flat search: a single "any" query with one offset/
|
||||
// count, results as Child, regardless of type.
|
||||
func handleSearch(rc *Context) *apiError {
|
||||
any, aerr := rc.Params.Required("any")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
offset := rc.Params.Int("offset", 0)
|
||||
count := rc.Params.Int("count", 20)
|
||||
|
||||
m, err := runSearch(db, any, offset, count, offset, count, offset, count)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
out := &SearchResult{Offset: offset}
|
||||
for _, a := range m.Artists {
|
||||
out.Match = append(out.Match, Child{ID: a.ID, IsDir: true, Title: a.Name})
|
||||
}
|
||||
for _, a := range m.Albums {
|
||||
out.Match = append(out.Match, childFromAlbum(db, a, rc.User.ID))
|
||||
}
|
||||
for _, s := range m.Songs {
|
||||
mf, merr := database.GetMediaFile(db, s.MediaFileID)
|
||||
if merr != nil {
|
||||
continue
|
||||
}
|
||||
out.Match = append(out.Match, childFromSong(db, s, mf, albumOfSong(db, s), rc.User.ID))
|
||||
}
|
||||
out.TotalHits = len(out.Match)
|
||||
rc.Response = newResponse()
|
||||
rc.Response.SearchResult = out
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSearch2(rc *Context) *apiError {
|
||||
query, aerr := rc.Params.Required("query")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
m, err := runSearch(db, query,
|
||||
rc.Params.Int("artistOffset", 0), rc.Params.Int("artistCount", 20),
|
||||
rc.Params.Int("albumOffset", 0), rc.Params.Int("albumCount", 20),
|
||||
rc.Params.Int("songOffset", 0), rc.Params.Int("songCount", 20))
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
out := &SearchResult2{}
|
||||
for _, a := range m.Artists {
|
||||
out.Artist = append(out.Artist, Artist{ID: a.ID, Name: a.Name})
|
||||
}
|
||||
for _, a := range m.Albums {
|
||||
out.Album = append(out.Album, childFromAlbum(db, a, rc.User.ID))
|
||||
}
|
||||
for _, s := range m.Songs {
|
||||
mf, merr := database.GetMediaFile(db, s.MediaFileID)
|
||||
if merr != nil {
|
||||
continue
|
||||
}
|
||||
out.Song = append(out.Song, childFromSong(db, s, mf, albumOfSong(db, s), rc.User.ID))
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
rc.Response.SearchResult2 = out
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSearch3(rc *Context) *apiError {
|
||||
query, aerr := rc.Params.Required("query")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
m, err := runSearch(db, query,
|
||||
rc.Params.Int("artistOffset", 0), rc.Params.Int("artistCount", 20),
|
||||
rc.Params.Int("albumOffset", 0), rc.Params.Int("albumCount", 20),
|
||||
rc.Params.Int("songOffset", 0), rc.Params.Int("songCount", 20))
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
out := &SearchResult3{}
|
||||
for _, a := range m.Artists {
|
||||
out.Artist = append(out.Artist, artistToID3(db, a, rc.User.ID))
|
||||
}
|
||||
for _, a := range m.Albums {
|
||||
out.Album = append(out.Album, albumToID3(db, a, rc.User.ID))
|
||||
}
|
||||
for _, s := range m.Songs {
|
||||
mf, merr := database.GetMediaFile(db, s.MediaFileID)
|
||||
if merr != nil {
|
||||
continue
|
||||
}
|
||||
out.Song = append(out.Song, childFromSong(db, s, mf, albumOfSong(db, s), rc.User.ID))
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
rc.Response.SearchResult3 = out
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user