diff --git a/client/mpd/djrandom.go b/client/mpd/djrandom.go index b5ef82db2c7ded3d1077312e4f492404f2e099a7..1fb6e49cbab7d47f4c4d4aa9f7954144e40d2aa5 100644 --- a/client/mpd/djrandom.go +++ b/client/mpd/djrandom.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log" + "net/http" "net/url" "strings" "sync" @@ -96,15 +97,55 @@ func (s *DJRandomSong) Open() (io.ReadCloser, error) { return nil, errors.New("no audio file") } - resp, err := s.client.GetRaw("/dl/" + s.audiofile.MD5) - if err != nil { - return nil, err + return &songReader{ + client: s.client, + url: "/dl/" + s.audiofile.MD5, + size: s.audiofile.Size, + }, nil +} + +// A streaming HTTP reader that can resume the download on error. +type songReader struct { + client *util.HttpClient + url string + pos, size int64 + resp *http.Response +} + +func (r *songReader) Read(buf []byte) (int, error) { + var err error + for i := 0; i < 2; i++ { + if r.pos >= r.size { + return 0, io.EOF + } + if r.resp == nil { + r.resp, err = r.client.GetRawRange(r.url, r.pos, r.size) + if err != nil { + return 0, err + } + if r.resp.StatusCode > 300 { + return 0, fmt.Errorf("HTTP error %d", r.resp.StatusCode) + } + } + + var n int + n, err = r.resp.Body.Read(buf) + if n > 0 { + r.pos += int64(n) + return n, err + } + log.Printf("HTTP read error: %v", err) + r.resp.Body.Close() + r.resp = nil } - if resp.StatusCode != 200 { - resp.Body.Close() - return nil, fmt.Errorf("HTTP error %d", resp.StatusCode) + return 0, err +} + +func (r *songReader) Close() error { + if r.resp != nil { + r.resp.Body.Close() } - return resp.Body, nil + return nil } type DJRandomDatabase struct { diff --git a/util/http_client.go b/util/http_client.go index 2b9e4243ecb779fdd943fe62afb30f1f486e9c67..5e1d571b1beb53ccf46b3af6880910084900cee0 100644 --- a/util/http_client.go +++ b/util/http_client.go @@ -116,6 +116,19 @@ func (h *HttpClient) GetRaw(url string) (*http.Response, error) { return h.bulkClient.Do(req) } +func (h *HttpClient) GetRawRange(url string, beg, end int64) (*http.Response, error) { + req, err := h.NewRequest("GET", url, nil) + if err != nil { + log.Printf("GetRaw(%s): NewRequest error: %s", url, err) + return nil, err + } + req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", beg, end-1)) + + SignHttpRequest(h.authKey, req) + + return h.bulkClient.Do(req) +} + func (h *HttpClient) NewRequest(method, relUrl string, data io.Reader) (*http.Request, error) { fullUrl := fmt.Sprintf("%s%s", h.baseUrl, relUrl) return http.NewRequest(method, fullUrl, data)