]> Sergey Matveev's repositories - btrtrc.git/blob - tracker_scraper.go
Rework tracker scraper code to allow tracker stats
[btrtrc.git] / tracker_scraper.go
1 package torrent
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "time"
8
9         "github.com/anacrolix/missinggo"
10
11         "github.com/anacrolix/torrent/tracker"
12 )
13
14 // Announces a torrent to a tracker at regular intervals, when peers are
15 // required.
16 type trackerScraper struct {
17         url string
18         // Causes the trackerScraper to stop running.
19         stop         missinggo.Event
20         t            *Torrent
21         lastAnnounce trackerAnnounceResult
22 }
23
24 func (ts *trackerScraper) statusLine() string {
25         var w bytes.Buffer
26         fmt.Fprintf(&w, "%q\t%s\t%s",
27                 ts.url,
28                 func() string {
29                         // return ts.lastAnnounce.Completed.Add(ts.lastAnnounce.Interval).Format("2006-01-02 15:04:05 -0700 MST")
30                         na := ts.lastAnnounce.Completed.Add(ts.lastAnnounce.Interval).Sub(time.Now())
31                         if na > 0 {
32                                 return na.String()
33                         } else {
34                                 return "anytime"
35                         }
36                 }(),
37                 func() string {
38                         if ts.lastAnnounce.Err != nil {
39                                 return ts.lastAnnounce.Err.Error()
40                         }
41                         return fmt.Sprintf("%d peers", ts.lastAnnounce.NumPeers)
42                 }())
43         return w.String()
44 }
45
46 type trackerAnnounceResult struct {
47         Err       error
48         NumPeers  int
49         Interval  time.Duration
50         Completed time.Time
51 }
52
53 func trackerToTorrentPeers(ps []tracker.Peer) (ret []Peer) {
54         ret = make([]Peer, 0, len(ps))
55         for _, p := range ps {
56                 ret = append(ret, Peer{
57                         IP:     p.IP,
58                         Port:   p.Port,
59                         Source: peerSourceTracker,
60                 })
61         }
62         return
63 }
64
65 // Return how long to wait before trying again. For most errors, we return 5
66 // minutes, a relatively quick turn around for DNS changes.
67 func (me *trackerScraper) announce() (ret trackerAnnounceResult) {
68         defer func() {
69                 ret.Completed = time.Now()
70         }()
71         ret.Interval = 5 * time.Minute
72         blocked, urlToUse, host, err := me.t.cl.prepareTrackerAnnounceUnlocked(me.url)
73         if err != nil {
74                 ret.Err = err
75                 return
76         }
77         if blocked {
78                 ret.Err = errors.New("blocked by IP")
79                 return
80         }
81         me.t.cl.mu.Lock()
82         req := me.t.announceRequest()
83         me.t.cl.mu.Unlock()
84         res, err := tracker.AnnounceHost(urlToUse, &req, host)
85         if err != nil {
86                 ret.Err = err
87                 return
88         }
89         me.t.AddPeers(trackerToTorrentPeers(res.Peers))
90         ret.NumPeers = len(res.Peers)
91         ret.Interval = time.Duration(res.Interval) * time.Second
92         return
93 }
94
95 func (me *trackerScraper) Run() {
96         for {
97                 select {
98                 case <-me.t.closed.LockedChan(&me.t.cl.mu):
99                         return
100                 case <-me.stop.LockedChan(&me.t.cl.mu):
101                         return
102                 case <-me.t.wantPeersEvent.LockedChan(&me.t.cl.mu):
103                 }
104
105                 ar := me.announce()
106                 me.t.cl.mu.Lock()
107                 me.lastAnnounce = ar
108                 me.t.cl.mu.Unlock()
109
110                 intervalChan := time.After(ar.Completed.Add(ar.Interval).Sub(time.Now()))
111
112                 select {
113                 case <-me.t.closed.LockedChan(&me.t.cl.mu):
114                         return
115                 case <-me.stop.LockedChan(&me.t.cl.mu):
116                         return
117                 case <-intervalChan:
118                 }
119         }
120 }