]> Sergey Matveev's repositories - btrtrc.git/blob - tracker_scraper.go
Adding Started and Stopped events
[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         "github.com/anacrolix/torrent/tracker"
14 )
15
16 // Announces a torrent to a tracker at regular intervals, when peers are
17 // required.
18 type trackerScraper struct {
19         u url.URL
20         // Causes the trackerScraper to stop running.
21         stop         missinggo.Event
22         t            *Torrent
23         lastAnnounce trackerAnnounceResult
24 }
25
26 func (ts *trackerScraper) statusLine() string {
27         var w bytes.Buffer
28         fmt.Fprintf(&w, "%q\t%s\t%s",
29                 ts.u.String(),
30                 func() string {
31                         na := time.Until(ts.lastAnnounce.Completed.Add(ts.lastAnnounce.Interval))
32                         if na > 0 {
33                                 na /= time.Second
34                                 na *= time.Second
35                                 return na.String()
36                         } else {
37                                 return "anytime"
38                         }
39                 }(),
40                 func() string {
41                         if ts.lastAnnounce.Err != nil {
42                                 return ts.lastAnnounce.Err.Error()
43                         }
44                         if ts.lastAnnounce.Completed.IsZero() {
45                                 return "never"
46                         }
47                         return fmt.Sprintf("%d peers", ts.lastAnnounce.NumPeers)
48                 }(),
49         )
50         return w.String()
51 }
52
53 type trackerAnnounceResult struct {
54         Err       error
55         NumPeers  int
56         Interval  time.Duration
57         Completed time.Time
58 }
59
60 func (me *trackerScraper) getIp() (ip net.IP, err error) {
61         ips, err := net.LookupIP(me.u.Hostname())
62         if err != nil {
63                 return
64         }
65         if len(ips) == 0 {
66                 err = errors.New("no ips")
67                 return
68         }
69         for _, ip = range ips {
70                 if me.t.cl.ipIsBlocked(ip) {
71                         continue
72                 }
73                 switch me.u.Scheme {
74                 case "udp4":
75                         if ip.To4() == nil {
76                                 continue
77                         }
78                 case "udp6":
79                         if ip.To4() != nil {
80                                 continue
81                         }
82                 }
83                 return
84         }
85         err = errors.New("no acceptable ips")
86         return
87 }
88
89 func (me *trackerScraper) trackerUrl(ip net.IP) string {
90         u := me.u
91         if u.Port() != "" {
92                 u.Host = net.JoinHostPort(ip.String(), u.Port())
93         }
94         return u.String()
95 }
96
97 // Return how long to wait before trying again. For most errors, we return 5
98 // minutes, a relatively quick turn around for DNS changes.
99 func (me *trackerScraper) announce(event tracker.AnnounceEvent) (ret trackerAnnounceResult) {
100         defer func() {
101                 ret.Completed = time.Now()
102         }()
103         ret.Interval = 5 * time.Minute
104         ip, err := me.getIp()
105         if err != nil {
106                 ret.Err = fmt.Errorf("error getting ip: %s", err)
107                 return
108         }
109         me.t.cl.lock()
110         req := me.t.announceRequest(event)
111         me.t.cl.unlock()
112         res, err := tracker.Announce{
113                 HTTPProxy:  me.t.cl.config.HTTPProxy,
114                 UserAgent:  me.t.cl.config.HTTPUserAgent,
115                 TrackerUrl: me.trackerUrl(ip),
116                 Request:    req,
117                 HostHeader: me.u.Host,
118                 ServerName: me.u.Hostname(),
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         // make sure first announce is a "started"
135         e := tracker.Started
136         for {
137                 ar := me.announce(e)
138                 // after first announce, get back to regular "none"
139                 e = tracker.None
140                 me.t.cl.lock()
141                 me.lastAnnounce = ar
142                 me.t.cl.unlock()
143
144         wait:
145                 interval := ar.Interval
146                 if interval < time.Minute {
147                         interval = time.Minute
148                 }
149                 wantPeers := me.t.wantPeersEvent.LockedChan(me.t.cl.locker())
150                 select {
151                 case <-wantPeers:
152                         if interval > time.Minute {
153                                 interval = time.Minute
154                         }
155                         wantPeers = nil
156                 default:
157                 }
158
159                 select {
160                 case <-me.t.closed.LockedChan(me.t.cl.locker()):
161                         return
162                 case <-me.stop.LockedChan(me.t.cl.locker()):
163                         return
164                 case <-wantPeers:
165                         goto wait
166                 case <-time.After(time.Until(ar.Completed.Add(interval))):
167                 }
168         }
169 }
170
171 func (me *trackerScraper) Stop() {
172         req := me.t.announceRequest(tracker.Stopped)
173         ip, _ := me.getIp()
174         tracker.Announce{
175                 HTTPProxy:  me.t.cl.config.HTTPProxy,
176                 UserAgent:  me.t.cl.config.HTTPUserAgent,
177                 TrackerUrl: me.trackerUrl(ip),
178                 Request:    req,
179                 HostHeader: me.u.Host,
180                 ServerName: me.u.Hostname(),
181         }.Do()
182 }