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 }