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  }