github.com/anacrolix/torrent@v1.61.0/sources.go (about) 1 package torrent 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand/v2" 7 "net/http" 8 "time" 9 10 g "github.com/anacrolix/generics" 11 "github.com/anacrolix/missinggo/v2/panicif" 12 13 "github.com/anacrolix/torrent/bencode" 14 "github.com/anacrolix/torrent/metainfo" 15 ) 16 17 // Add HTTP endpoints that serve the metainfo. They will be used if the torrent info isn't obtained 18 // yet. The Client HTTP client is used. 19 func (t *Torrent) AddSources(sources []string) { 20 for _, s := range sources { 21 _, loaded := t.activeSources.LoadOrStore(s, nil) 22 if loaded { 23 continue 24 } 25 go t.sourcer(s) 26 } 27 } 28 29 // Tries fetching metainfo from a (HTTP) source until no longer necessary. 30 func (t *Torrent) sourcer(source string) { 31 var err error 32 defer func() { 33 panicif.False(t.activeSources.CompareAndSwap(source, nil, err)) 34 }() 35 ctx := t.getInfoCtx 36 for { 37 var retry g.Option[time.Duration] 38 retry, err = t.trySource(source) 39 if err == nil || ctx.Err() != nil { 40 return 41 } 42 t.slogger().Warn("error using torrent source", "source", source, "err", err) 43 if !retry.Ok { 44 return 45 } 46 select { 47 case <-time.After(retry.Unwrap()): 48 case <-ctx.Done(): 49 } 50 } 51 } 52 53 // If retry is None, take the error you get as final. 54 func (t *Torrent) trySource(source string) (retry g.Option[time.Duration], err error) { 55 t.sourceMutex.Lock() 56 defer t.sourceMutex.Unlock() 57 ctx := t.getInfoCtx 58 if ctx.Err() != nil { 59 return 60 } 61 var mi metainfo.MetaInfo 62 mi, err = getTorrentSource(ctx, source, t.cl.config.MetainfoSourcesClient) 63 if ctx.Err() != nil { 64 return 65 } 66 if err != nil { 67 retry.Set(time.Minute + time.Duration(rand.Int64N(int64(time.Minute)))) 68 return 69 } 70 err = t.cl.config.MetainfoSourcesMerger(t, &mi) 71 return 72 } 73 74 func getTorrentSource(ctx context.Context, source string, hc *http.Client) (mi metainfo.MetaInfo, err error) { 75 var req *http.Request 76 if req, err = http.NewRequestWithContext(ctx, http.MethodGet, source, nil); err != nil { 77 return 78 } 79 var resp *http.Response 80 if resp, err = hc.Do(req); err != nil { 81 return 82 } 83 defer resp.Body.Close() 84 if resp.StatusCode != http.StatusOK { 85 err = fmt.Errorf("unexpected response status code: %v", resp.StatusCode) 86 return 87 } 88 err = bencode.NewDecoder(resp.Body).Decode(&mi) 89 return 90 }