15 connIdIssued time.Time
16 Dispatcher *Dispatcher
20 func (cl *Client) Announce(
21 ctx context.Context, req AnnounceRequest, peers AnnounceResponsePeers, opts Options,
23 respHdr AnnounceResponseHeader, err error,
25 respBody, err := cl.request(ctx, ActionAnnounce, append(mustMarshal(req), opts.Encode()...))
29 r := bytes.NewBuffer(respBody)
30 err = Read(r, &respHdr)
32 err = fmt.Errorf("reading response header: %w", err)
35 err = peers.UnmarshalBinary(r.Bytes())
37 err = fmt.Errorf("reading response peers: %w", err)
42 func (cl *Client) Scrape(
43 ctx context.Context, ihs []InfoHash,
45 out ScrapeResponse, err error,
47 // There's no way to pass options in a scrape, since we don't when the request body ends.
48 respBody, err := cl.request(ctx, ActionScrape, mustMarshal(ScrapeRequest(ihs)))
52 r := bytes.NewBuffer(respBody)
54 var item ScrapeInfohashResult
59 out = append(out, item)
61 if len(out) > len(ihs) {
62 err = fmt.Errorf("got %v results but expected %v", len(out), len(ihs))
68 func (cl *Client) connect(ctx context.Context) (err error) {
69 if !cl.connIdIssued.IsZero() && time.Since(cl.connIdIssued) < time.Minute {
72 respBody, err := cl.request(ctx, ActionConnect, nil)
76 var connResp ConnectionResponse
77 err = binary.Read(bytes.NewReader(respBody), binary.BigEndian, &connResp)
81 cl.connId = connResp.ConnectionId
82 cl.connIdIssued = time.Now()
86 func (cl *Client) connIdForRequest(ctx context.Context, action Action) (id ConnectionId, err error) {
87 if action == ActionConnect {
88 id = ConnectRequestConnectionId
99 func (cl *Client) requestWriter(ctx context.Context, action Action, body []byte, tId TransactionId) (err error) {
102 var connId ConnectionId
103 connId, err = cl.connIdForRequest(ctx, action)
108 err = binary.Write(&buf, binary.BigEndian, RequestHeader{
109 ConnectionId: connId,
117 _, err = cl.Writer.Write(buf.Bytes())
124 case <-time.After(timeout(n)):
129 func (cl *Client) request(ctx context.Context, action Action, body []byte) (respBody []byte, err error) {
130 respChan := make(chan DispatchedResponse, 1)
131 t := cl.Dispatcher.NewTransaction(func(dr DispatchedResponse) {
135 ctx, cancel := context.WithCancel(ctx)
137 writeErr := make(chan error, 1)
139 writeErr <- cl.requestWriter(ctx, action, body, t.Id())
142 case dr := <-respChan:
143 if dr.Header.Action == action {
145 } else if dr.Header.Action == ActionError {
146 err = errors.New(string(dr.Body))
148 err = fmt.Errorf("unexpected response action %v", dr.Header.Action)
150 case err = <-writeErr:
151 err = fmt.Errorf("write error: %w", err)