Skip to content
This repository was archived by the owner on Dec 28, 2024. It is now read-only.

Commit ed0d874

Browse files
authored
Merge pull request #5 from defsub/singles
Singles
2 parents a93f2c2 + b6758e6 commit ed0d874

12 files changed

Lines changed: 234 additions & 126 deletions

File tree

client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ func (c *Client) doGet(headers map[string]string, urlStr string) (*http.Response
9999
}
100100
if cachedResp != nil {
101101
throttle = false
102+
log.Printf("is cached\n")
102103
}
103104
}
104105
if throttle {
106+
log.Printf("rate limit\n")
105107
RateLimit(url.Hostname())
106108
}
107109

config/config.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ type SearchConfig struct {
9292
type ServerConfig struct {
9393
Listen string
9494
WebDir string
95+
URL string
9596
}
9697

9798
type ClientConfig struct {
@@ -147,9 +148,9 @@ func configDefaults() {
147148
viper.SetDefault("Music.SimilarReleases", "8760h") // +/- 1 year
148149
viper.SetDefault("Music.SimilarReleasesLimit", "10")
149150

150-
viper.SetDefault("Music.RadioLimit", "250")
151+
viper.SetDefault("Music.RadioLimit", "25")
151152
viper.SetDefault("Music.RadioSearchLimit", "1000")
152-
viper.SetDefault("Music.ArtistRadioBreadth", "25")
153+
viper.SetDefault("Music.ArtistRadioBreadth", "10")
153154
viper.SetDefault("Music.ArtistRadioDepth", "10")
154155

155156
// see https://wiki.musicbrainz.org/Release_Country
@@ -195,6 +196,8 @@ func configDefaults() {
195196
"r&b",
196197
"rock",
197198
"shoegaze",
199+
"stoner metal",
200+
"stoner rock",
198201
})
199202

200203
viper.SetDefault("Music.Bucket.UseSSL", "true")
@@ -210,6 +213,7 @@ func configDefaults() {
210213
viper.SetDefault("Search.BleveDir", ".")
211214

212215
viper.SetDefault("Server.WebDir", "web")
216+
viper.SetDefault("Server.URL", "https://example.com") // w/o trailing slash
213217

214218
viper.SetDefault("Client.UseCache", "false")
215219
viper.SetDefault("Client.MaxAge", 86400*30) // 30 days in seconds

music/api.go

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,9 @@ import (
2626
"net/http"
2727
"regexp"
2828
"strconv"
29-
"time"
29+
"strings"
3030
)
3131

32-
type location struct {
33-
ID uint
34-
Url string
35-
Size int64
36-
ETag string
37-
LastModified time.Time
38-
}
39-
4032
type login struct {
4133
User string
4234
Pass string
@@ -223,9 +215,12 @@ func (handler *MusicHandler) apiStation(w http.ResponseWriter, r *http.Request,
223215
// GET /api/{res}/id/playlist > spiff.Playlist{}
224216
// 200: success
225217
func (handler *MusicHandler) apiRefPlaylist(w http.ResponseWriter, r *http.Request, m *Music,
226-
title, ref string) {
218+
creator, title, image, ref string) {
227219
plist := spiff.NewPlaylist()
220+
plist.Spiff.Location = fmt.Sprintf("%s%s", m.config.Server.URL, r.URL.Path)
221+
plist.Spiff.Creator = creator
228222
plist.Spiff.Title = title
223+
plist.Spiff.Image = image
229224
plist.Entries = []spiff.Entry{{Ref: ref}}
230225
m.Resolve(handler.user, plist)
231226
if plist.Entries == nil {
@@ -247,7 +242,9 @@ func (handler *MusicHandler) apiRefPlaylist(w http.ResponseWriter, r *http.Reque
247242
func (handler *MusicHandler) apiPlaylist(w http.ResponseWriter, r *http.Request, music *Music) {
248243
p := music.lookupPlaylist(handler.user)
249244
if p == nil {
250-
data, _ := spiff.NewPlaylist().Marshal()
245+
plist := spiff.NewPlaylist()
246+
plist.Spiff.Location = fmt.Sprintf("%s/api/playlist", music.config.Server.URL)
247+
data, _ := plist.Marshal()
251248
p = &Playlist{User: handler.user.Name, Playlist: data}
252249
err := music.createPlaylist(p)
253250
if err != nil {
@@ -299,16 +296,40 @@ func (handler *MusicHandler) apiView(w http.ResponseWriter, r *http.Request, vie
299296
enc.Encode(view)
300297
}
301298

299+
func (handler *MusicHandler) apiSearch(w http.ResponseWriter, r *http.Request, music *Music) {
300+
if v := r.URL.Query().Get("q"); v != "" {
301+
// /api/search?q={pattern}
302+
view := music.SearchView(strings.TrimSpace(v))
303+
handler.apiView(w, r, view)
304+
} else {
305+
http.Error(w, "bummer", http.StatusNotFound)
306+
}
307+
}
308+
302309
// POST /api/login -> see apiLogin
310+
//
303311
// GET,PATCH /api/playlist -> see apiPlaylist
304312
//
313+
// GET /api/radio > RadioView{}
314+
// GET /api/radio/1 > spiff.Playlist{}
315+
// POST /api/radio
316+
// GET,PATH,DELETE /api/radio/1 >
317+
//
305318
// GET /api/home > HomeView{}
319+
// GET /api/search > SearchView{}
320+
//
321+
// GET /api/tracks/1/location -> Redirect
322+
//
306323
// GET /api/artists > ArtistsView{}
307324
// GET /api/artists/1 > ArtistView{}
325+
// GET /api/artists/1/popular > PopularView{}
326+
// GET /api/artists/1/singles > SinglesView{}
327+
// GET /api/artists/1/playlist > spiff.Playlist{}
328+
// GET /api/artists/1/radio > spiff.Playlist{}
329+
//
308330
// GET /api/releases/1 > ReleaseView{}
309-
// GET,POST /api/radio
310-
// GET,PATH,DELETE /api/radio/1 >
311-
// GET /api/tracks/1/location -> location{}
331+
// GET /api/releases/1/playlist > spiff.Playlist{}
332+
//
312333
// 200: success
313334
// 500: error
314335
func (handler *MusicHandler) apiHandler(w http.ResponseWriter, r *http.Request) {
@@ -336,6 +357,8 @@ func (handler *MusicHandler) apiHandler(w http.ResponseWriter, r *http.Request)
336357
handler.apiView(w, r, music.HomeView())
337358
case "/api/artists":
338359
handler.apiView(w, r, music.ArtistsView())
360+
case "/api/search":
361+
handler.apiSearch(w, r, music)
339362
default:
340363
// id sub-resources
341364
locationRegexp := regexp.MustCompile(`/api/tracks/([0-9]+)/location`)
@@ -345,35 +368,54 @@ func (handler *MusicHandler) apiHandler(w http.ResponseWriter, r *http.Request)
345368
id, _ := strconv.Atoi(v)
346369
track, _ := music.lookupTrack(id)
347370
url := music.TrackURL(&track)
348-
handler.apiView(w, r, location{
349-
ID: track.ID,
350-
Url: url.String(),
351-
Size: track.Size,
352-
ETag: track.ETag,
353-
LastModified: track.LastModified,
354-
})
371+
// TODO use 307 instead?
372+
http.Redirect(w, r, url.String(), http.StatusFound)
355373
return
356374
}
357375

358-
// resources with id and playlist
359-
playlistRegexp := regexp.MustCompile(`/api/([a-z]+)/([0-9]+)/playlist`)
376+
// resources with id and sub-resource
377+
playlistRegexp := regexp.MustCompile(`/api/([a-z]+)/([0-9]+)/(playlist|popular|singles|radio)`)
360378
matches = playlistRegexp.FindStringSubmatch(r.URL.Path)
361379
if matches != nil {
362380
v := matches[1]
363381
id, _ := strconv.Atoi(matches[2])
382+
res := matches[3]
364383
switch v {
365384
case "artists":
366-
// /api/artists/1/playlist
367385
artist, _ := music.lookupArtist(id)
368-
handler.apiRefPlaylist(w, r, music,
369-
fmt.Sprintf("%s", artist.Name),
370-
fmt.Sprintf("/music/artists/%d/shuffle", id))
386+
if res == "playlist" {
387+
// /api/artists/1/playlist
388+
handler.apiRefPlaylist(w, r, music,
389+
artist.Name,
390+
"Top Tracks",
391+
"",
392+
fmt.Sprintf("/music/artists/%d/popular", id))
393+
} else if res == "radio" {
394+
// /api/artists/1/radio
395+
handler.apiRefPlaylist(w, r, music,
396+
"Radio",
397+
fmt.Sprintf("%s Radio", artist.Name),
398+
"",
399+
fmt.Sprintf("/music/artists/%d/similar", id))
400+
} else if res == "popular" {
401+
// /api/artists/1/popular
402+
handler.apiView(w, r, music.PopularView(artist))
403+
} else if res == "singles" {
404+
// /api/artists/1/singles
405+
handler.apiView(w, r, music.SinglesView(artist))
406+
}
371407
case "releases":
372408
// /api/releases/1/playlist
373-
release, _ := music.lookupRelease(id)
374-
handler.apiRefPlaylist(w, r, music,
375-
fmt.Sprintf("%s ~ %s", release.Artist, release.Name),
376-
fmt.Sprintf("/music/releases/%d/tracks", id))
409+
if res == "playlist" {
410+
release, _ := music.lookupRelease(id)
411+
handler.apiRefPlaylist(w, r, music,
412+
release.Artist,
413+
release.Name,
414+
music.cover(release, "250"),
415+
fmt.Sprintf("/music/releases/%d/tracks", id))
416+
} else {
417+
http.Error(w, "bummer", http.StatusNotFound)
418+
}
377419
default:
378420
http.Error(w, "bummer", http.StatusNotFound)
379421
}

music/credits.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
FieldLength = "length"
3535
FieldMedia = "media"
3636
FieldMediaTitle = "media_title"
37+
FieldPopularity = "popularity"
3738
FieldRating = "rating"
3839
FieldRelease = "release"
3940
FieldReleaseDate = "release_date"

music/db.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,11 @@ func (m *Music) trackRelease(t *Track) *Release {
302302
return &releases[0]
303303
}
304304

305-
// Find the first release date for the release(s) with` this track, including
305+
// Find the first release date for the release(s) with this track, including
306306
// an media specific release from a multi-disc set like: Eagles/Legacy or The
307307
// Beatles/The Beatles in Mono. These each have media with titles that
308308
// themselves were previous releases so check them too.
309-
func (m *Music) trackFirstReleaseDate(t *Track) time.Time {
310-
result := time.Time{}
309+
func (m *Music) trackFirstReleaseDate(t *Track) (result time.Time, err error) {
311310
var releases []Release
312311
names := []string{t.Release}
313312
if t.MediaTitle != "" {
@@ -321,6 +320,7 @@ func (m *Music) trackFirstReleaseDate(t *Track) time.Time {
321320
Order("date").Find(&releases)
322321
if len(releases) > 0 {
323322
result = releases[0].Date
323+
err = nil
324324
} else {
325325
// could be disambiguation like "Weezer (Blue Album)" so just
326326
// use release date for now
@@ -329,7 +329,7 @@ func (m *Music) trackFirstReleaseDate(t *Track) time.Time {
329329
result = r.Date
330330
}
331331
}
332-
return result
332+
return result, err
333333
}
334334

335335
// When there's artwork but no front, other_cover will be the ID of the image

music/music.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,17 @@ func (m *Music) releaseIndex(release Release) (search.IndexMap, error) {
756756
}
757757
}
758758

759+
// Popular artist tracks mapped to the first release where the tracks
760+
// appeared. If this is that release, add popularty fields for those
761+
// tracks below.
762+
popularityMap := make(map[string]int)
763+
a := m.artist(release.Artist)
764+
if a != nil {
765+
for rank, t := range m.artistPopularTracks(*a) {
766+
popularityMap[t.Key] = rank+1
767+
}
768+
}
769+
759770
// update type field with single
760771
singles := make(map[string]bool)
761772
for _, t := range m.releaseSingles(release) {
@@ -777,13 +788,23 @@ func (m *Music) releaseIndex(release Release) (search.IndexMap, error) {
777788
fields, ok := newIndex[k]
778789
if ok {
779790
addField(fields, FieldType, TypePopular)
791+
792+
rank, pop := popularityMap[k]
793+
if pop {
794+
// add popularity rank
795+
log.Printf("popularity %s -> %d", k, rank)
796+
addField(fields, FieldPopularity, rank)
797+
}
780798
}
781799
}
782800

783801
// use first track release date for tracks index
784802
for k, v := range newIndex {
785803
tracks := m.tracksFor([]string{k})
786-
date := m.trackFirstReleaseDate(&tracks[0])
804+
date, err := m.trackFirstReleaseDate(&tracks[0])
805+
if err != nil {
806+
continue
807+
}
787808
s := fmt.Sprintf("%4d-%02d-%02d", date.Year(), date.Month(), date.Day())
788809
addField(v, FieldDate, s)
789810
}

0 commit comments

Comments
 (0)