]> Sergey Matveev's repositories - btrtrc.git/blob - webseed-peer.go
Pass tests with new full-client request strategy implementation
[btrtrc.git] / webseed-peer.go
1 package torrent
2
3 import (
4         "context"
5         "errors"
6         "fmt"
7         "net/http"
8         "strings"
9         "sync"
10
11         "github.com/anacrolix/torrent/common"
12         "github.com/anacrolix/torrent/metainfo"
13         pp "github.com/anacrolix/torrent/peer_protocol"
14         "github.com/anacrolix/torrent/segments"
15         "github.com/anacrolix/torrent/webseed"
16 )
17
18 type webseedPeer struct {
19         client         webseed.Client
20         activeRequests map[Request]webseed.Request
21         requesterCond  sync.Cond
22         peer           Peer
23 }
24
25 var _ peerImpl = (*webseedPeer)(nil)
26
27 func (me *webseedPeer) writeBufferFull() bool {
28         return false
29 }
30
31 func (me *webseedPeer) connStatusString() string {
32         return me.client.Url
33 }
34
35 func (ws *webseedPeer) String() string {
36         return fmt.Sprintf("webseed peer for %q", ws.client.Url)
37 }
38
39 func (ws *webseedPeer) onGotInfo(info *metainfo.Info) {
40         ws.client.FileIndex = segments.NewIndex(common.LengthIterFromUpvertedFiles(info.UpvertedFiles()))
41         ws.client.Info = info
42 }
43
44 func (ws *webseedPeer) _postCancel(r Request) {
45         ws.cancel(r)
46 }
47
48 func (ws *webseedPeer) writeInterested(interested bool) bool {
49         return true
50 }
51
52 func (ws *webseedPeer) cancel(r Request) bool {
53         active, ok := ws.activeRequests[r]
54         if !ok {
55                 return false
56         }
57         active.Cancel()
58         return true
59 }
60
61 func (ws *webseedPeer) intoSpec(r Request) webseed.RequestSpec {
62         return webseed.RequestSpec{ws.peer.t.requestOffset(r), int64(r.Length)}
63 }
64
65 func (ws *webseedPeer) request(r Request) bool {
66         ws.requesterCond.Signal()
67         return true
68 }
69
70 func (ws *webseedPeer) doRequest(r Request) {
71         webseedRequest := ws.client.NewRequest(ws.intoSpec(r))
72         ws.activeRequests[r] = webseedRequest
73         func() {
74                 ws.requesterCond.L.Unlock()
75                 defer ws.requesterCond.L.Lock()
76                 ws.requestResultHandler(r, webseedRequest)
77         }()
78         delete(ws.activeRequests, r)
79 }
80
81 func (ws *webseedPeer) requester() {
82         ws.requesterCond.L.Lock()
83         defer ws.requesterCond.L.Unlock()
84 start:
85         for !ws.peer.closed.IsSet() {
86                 for r := range ws.peer.requests {
87                         if _, ok := ws.activeRequests[r]; ok {
88                                 continue
89                         }
90                         ws.doRequest(r)
91                         goto start
92                 }
93                 ws.requesterCond.Wait()
94         }
95 }
96
97 func (ws *webseedPeer) connectionFlags() string {
98         return "WS"
99 }
100
101 // TODO: This is called when banning peers. Perhaps we want to be able to ban webseeds too. We could
102 // return bool if this is even possible, and if it isn't, skip to the next drop candidate.
103 func (ws *webseedPeer) drop() {}
104
105 func (ws *webseedPeer) updateRequests() {
106 }
107
108 func (ws *webseedPeer) onClose() {
109         ws.peer.logger.Print("closing")
110         for _, r := range ws.activeRequests {
111                 r.Cancel()
112         }
113         ws.requesterCond.Broadcast()
114 }
115
116 func (ws *webseedPeer) requestResultHandler(r Request, webseedRequest webseed.Request) {
117         result := <-webseedRequest.Result
118         ws.peer.t.cl.lock()
119         defer ws.peer.t.cl.unlock()
120         if result.Err != nil {
121                 if !errors.Is(result.Err, context.Canceled) {
122                         ws.peer.logger.Printf("Request %v rejected: %v", r, result.Err)
123                 }
124                 // We need to filter out temporary errors, but this is a nightmare in Go. Currently a bad
125                 // webseed URL can starve out the good ones due to the chunk selection algorithm.
126                 const closeOnAllErrors = false
127                 if closeOnAllErrors ||
128                         strings.Contains(result.Err.Error(), "unsupported protocol scheme") ||
129                         func() bool {
130                                 var err webseed.ErrBadResponse
131                                 if !errors.As(result.Err, &err) {
132                                         return false
133                                 }
134                                 return err.Response.StatusCode == http.StatusNotFound
135                         }() {
136                         ws.peer.close()
137                 } else {
138                         ws.peer.remoteRejectedRequest(r)
139                 }
140         } else {
141                 err := ws.peer.receiveChunk(&pp.Message{
142                         Type:  pp.Piece,
143                         Index: r.Index,
144                         Begin: r.Begin,
145                         Piece: result.Bytes,
146                 })
147                 if err != nil {
148                         panic(err)
149                 }
150         }
151 }