]> Sergey Matveev's repositories - btrtrc.git/blob - tracker_scraper.go
Law of Demeter Client.mu
[btrtrc.git] / tracker_scraper.go
1 package torrent
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "net"
8         "net/url"
9         "time"
10
11         "github.com/anacrolix/dht/krpc"
12         "github.com/anacrolix/missinggo"
13
14         "github.com/anacrolix/torrent/tracker"
15 )
16
17 // Announces a torrent to a tracker at regular intervals, when peers are
18 // required.
19 type trackerScraper struct {
20         u url.URL
21         // Causes the trackerScraper to stop running.
22         stop         missinggo.Event
23         t            *Torrent
24         lastAnnounce trackerAnnounceResult
25 }
26
27 func (ts *trackerScraper) statusLine() string {
28         var w bytes.Buffer
29         fmt.Fprintf(&w, "%q\t%s\t%s",
30                 ts.u.String(),
31                 func() string {
32                         na := time.Until(ts.lastAnnounce.Completed.Add(ts.lastAnnounce.Interval))
33                         if na > 0 {
34                                 na /= time.Second
35                                 na *= time.Second
36                                 return na.String()
37                         } else {
38                                 return "anytime"
39                         }
40                 }(),
41                 func() string {
42                         if ts.lastAnnounce.Err != nil {
43                                 return ts.lastAnnounce.Err.Error()
44                         }
45                         if ts.lastAnnounce.Completed.IsZero() {
46                                 return "never"
47                         }
48                         return fmt.Sprintf("%d peers", ts.lastAnnounce.NumPeers)
49                 }(),
50         )
51         return w.String()
52 }
53
54 type trackerAnnounceResult struct {
55         Err       error
56         NumPeers  int
57         Interval  time.Duration
58         Completed time.Time
59 }
60
61 func (me *trackerScraper) getIp() (ip net.IP, err error) {
62         ips, err := net.LookupIP(me.u.Hostname())
63         if err != nil {
64                 return
65         }
66         if len(ips) == 0 {
67                 err = errors.New("no ips")
68                 return
69         }
70         for _, ip = range ips {
71                 if me.t.cl.ipIsBlocked(ip) {
72                         continue
73                 }
74                 switch me.u.Scheme {
75                 case "udp4":
76                         if ip.To4() == nil {
77                                 continue
78                         }
79                 case "udp6":
80                         if ip.To4() != nil {
81                                 continue
82                         }
83                 }
84                 return
85         }
86         err = errors.New("no acceptable ips")
87         return
88 }
89
90 func (me *trackerScraper) trackerUrl(ip net.IP) string {
91         u := me.u
92         if u.Port() != "" {
93                 u.Host = net.JoinHostPort(ip.String(), u.Port())
94         }
95         return u.String()
96 }
97
98 // Return how long to wait before trying again. For most errors, we return 5
99 // minutes, a relatively quick turn around for DNS changes.
100 func (me *trackerScraper) announce() (ret trackerAnnounceResult) {
101         defer func() {
102                 ret.Completed = time.Now()
103         }()
104         ret.Interval = 5 * time.Minute
105         ip, err := me.getIp()
106         if err != nil {
107                 ret.Err = fmt.Errorf("error getting ip: %s", err)
108                 return
109         }
110         me.t.cl.lock()
111         req := me.t.announceRequest()
112         me.t.cl.unlock()
113         res, err := tracker.Announce{
114                 HttpClient: me.t.cl.config.TrackerHttpClient,
115                 UserAgent:  me.t.cl.config.HTTPUserAgent,
116                 TrackerUrl: me.trackerUrl(ip),
117                 Request:    req,
118                 HostHeader: me.u.Host,
119                 UdpNetwork: me.u.Scheme,
120                 ClientIp4:  krpc.NodeAddr{IP: me.t.cl.config.PublicIp4},
121                 ClientIp6:  krpc.NodeAddr{IP: me.t.cl.config.PublicIp6},
122         }.Do()
123         if err != nil {
124                 ret.Err = fmt.Errorf("error announcing: %s", err)
125                 return
126         }
127         me.t.AddPeers(Peers(nil).AppendFromTracker(res.Peers))
128         ret.NumPeers = len(res.Peers)
129         ret.Interval = time.Duration(res.Interval) * time.Second
130         return
131 }
132
133 func (me *trackerScraper) Run() {
134         for {
135                 select {
136                 case <-me.t.closed.LockedChan(me.t.cl.locker()):
137                         return
138                 case <-me.stop.LockedChan(me.t.cl.locker()):
139                         return
140                 case <-me.t.wantPeersEvent.LockedChan(me.t.cl.locker()):
141                 }
142
143                 ar := me.announce()
144                 me.t.cl.lock()
145                 me.lastAnnounce = ar
146                 me.t.cl.unlock()
147
148                 intervalChan := time.After(time.Until(ar.Completed.Add(ar.Interval)))
149
150                 select {
151                 case <-me.t.closed.LockedChan(me.t.cl.locker()):
152                         return
153                 case <-me.stop.LockedChan(me.t.cl.locker()):
154                         return
155                 case <-intervalChan:
156                 }
157         }
158 }