aboutsummaryrefslogtreecommitdiff
path: root/tracker/messages.go
blob: 67c49c37a6c5a85d5864f1b26dcc745b7138edf6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package tracker

import (
	"crypto/sha1"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"time"

	"dim13.org/btget/bencode"
	"dim13.org/btget/query"
)

const DefaultInterval = 30 * time.Minute

type Event string

const (
	NoEvent   Event = ""
	Started   Event = "started"
	Stopped   Event = "stopped"
	Completed Event = "completed"
)

type Request struct {
	InfoHash   [sha1.Size]byte `query:"info_hash"` // info_hash
	PeerID     []byte          `query:"peer_id"`   // peer_id
	Port       int             `query:"port"`
	Uploaded   int             `query:"uploaded"`
	Downloaded int             `query:"downloaded"`
	Left       int             `query:"left"`
	Compact    bool            `query:"compact,optional"` // always true
	NoPeerID   bool            `query:"no_peer_id,optional"`
	Event      Event           `query:"event,optional"`
	IP         net.IPAddr      `query:"ip,optional"`
	NumWant    int             `query:"numwant,optional"`
	Key        []byte          `query:"key,optional"`
	TrackerID  []byte          `query:"tracker_id,optional"`
}

// we support only compact mode
type Response struct {
	Complete       int           `bencode:"complete"`
	FailureReason  string        `bencode:"failure reason"`
	Incomplete     int           `bencode:"incomplete"`
	Interval       time.Duration `bencode:"interval"`
	MinInterval    time.Duration `bencode:"min interval"`
	Peers          Peers         `bencode:"peers"` // can be []byte or []Peer
	Peers6         []byte        `bencode:"peers6"`
	TrackerID      string        `bencode:"tracker id"`
	WarningMessage string        `bencode:"warning message"`
}

func (r *Request) Poll(announce string) chan Peer {
	c := make(chan Peer)
	go func() {
		defer close(c)
		for {
			resp, err := r.Send(announce)
			if err != nil {
				log.Println(err)
				return
			}
			if msg := resp.WarningMessage; msg != "" {
				log.Println("warning:", msg)
			}
			if msg := resp.FailureReason; msg != "" {
				log.Println("failure:", msg)
				return
			}
			for _, p := range resp.Peers {
				c <- p
			}
			if resp.Interval == 0 {
				if resp.MinInterval > 0 {
					resp.Interval = resp.MinInterval
				} else {
					resp.Interval = DefaultInterval
				}
			}
			log.Println("waiting", resp.Interval)
			time.Sleep(resp.Interval)
		}
	}()
	return c
}

func (r Request) Send(announce string) (Response, error) {
	fail := func(err error) (Response, error) {
		return Response{}, err
	}
	q, err := query.Marshal(r)
	if err != nil {
		return fail(err)
	}
	client := &http.Client{Timeout: time.Minute}
	resp, err := client.Get(announce + q)
	if err != nil {
		return fail(err)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fail(err)
	}

	var res Response
	_, err = bencode.Unmarshal(body, &res)
	if err != nil {
		return fail(err)
	}
	return res, nil
}