11 "github.com/anacrolix/torrent/metainfo"
12 "github.com/anacrolix/torrent/segments"
15 type RequestSpec = segments.Extent
17 type requestPartResult struct {
22 type requestPart struct {
25 result chan requestPartResult
30 Result chan RequestResult
33 func (r Request) Cancel() {
38 HttpClient *http.Client
40 FileIndex segments.Index
44 type RequestResult struct {
49 func (ws *Client) NewRequest(r RequestSpec) Request {
50 ctx, cancel := context.WithCancel(context.Background())
51 var requestParts []requestPart
52 if !ws.FileIndex.Locate(r, func(i int, e segments.Extent) bool {
53 req, err := NewRequest(ws.Url, i, ws.Info, e.Start, e.Length)
57 req = req.WithContext(ctx)
60 result: make(chan requestPartResult, 1),
64 resp, err := ws.HttpClient.Do(req)
65 part.result <- requestPartResult{
70 requestParts = append(requestParts, part)
73 panic("request out of file bounds")
77 Result: make(chan RequestResult, 1),
80 b, err := readRequestPartResponses(requestParts)
81 req.Result <- RequestResult{
89 func recvPartResult(buf io.Writer, part requestPart) error {
90 result := <-part.result
91 if result.err != nil {
94 defer result.resp.Body.Close()
95 switch result.resp.StatusCode {
96 case http.StatusPartialContent:
98 if part.e.Start != 0 {
99 return errors.New("got status ok but request was at offset")
102 return fmt.Errorf("unhandled response status code (%v)", result.resp.StatusCode)
104 copied, err := io.Copy(buf, result.resp.Body)
108 if copied != part.e.Length {
109 return fmt.Errorf("got %v bytes, expected %v", copied, part.e.Length)
114 func readRequestPartResponses(parts []requestPart) ([]byte, error) {
116 for _, part := range parts {
117 err := recvPartResult(&buf, part)
119 return buf.Bytes(), fmt.Errorf("reading %q at %q: %w", part.req.URL, part.req.Header.Get("Range"), err)
122 return buf.Bytes(), nil