Files
mochi/tracker/announce.go
Jimmy Zelinskie 9862a57b73 remove all private tracker logic
There are no consumers of any of this logic nor is it complete. We're
better off without it in the meantime until we have a use case and a
more cohesive model for expressing it.
2016-01-04 18:26:29 -05:00

236 lines
5.9 KiB
Go

// Copyright 2015 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.
package tracker
import (
"github.com/chihaya/chihaya/stats"
"github.com/chihaya/chihaya/tracker/models"
)
// HandleAnnounce encapsulates all of the logic of handling a BitTorrent
// client's Announce without being coupled to any transport protocol.
func (tkr *Tracker) HandleAnnounce(ann *models.Announce, w Writer) (err error) {
if tkr.Config.ClientWhitelistEnabled {
if err = tkr.ClientApproved(ann.ClientID()); err != nil {
return err
}
}
torrent, err := tkr.FindTorrent(ann.Infohash)
if err == models.ErrTorrentDNE && tkr.Config.CreateOnAnnounce {
torrent = &models.Torrent{
Infohash: ann.Infohash,
Seeders: models.NewPeerMap(true, tkr.Config),
Leechers: models.NewPeerMap(false, tkr.Config),
}
tkr.PutTorrent(torrent)
stats.RecordEvent(stats.NewTorrent)
} else if err != nil {
return err
}
ann.BuildPeer(torrent)
_, err = tkr.updateSwarm(ann)
if err != nil {
return err
}
_, err = tkr.handleEvent(ann)
if err != nil {
return err
}
if tkr.Config.PurgeInactiveTorrents && torrent.PeerCount() == 0 {
// Rather than deleting the torrent explicitly, let the tracker driver
// ensure there are no race conditions.
tkr.PurgeInactiveTorrent(torrent.Infohash)
stats.RecordEvent(stats.DeletedTorrent)
}
stats.RecordEvent(stats.Announce)
return w.WriteAnnounce(newAnnounceResponse(ann))
}
// updateSwarm handles the changes to a torrent's swarm given an announce.
func (tkr *Tracker) updateSwarm(ann *models.Announce) (created bool, err error) {
var createdv4, createdv6 bool
tkr.TouchTorrent(ann.Torrent.Infohash)
if ann.HasIPv4() {
createdv4, err = tkr.updatePeer(ann, ann.PeerV4)
if err != nil {
return
}
}
if ann.HasIPv6() {
createdv6, err = tkr.updatePeer(ann, ann.PeerV6)
if err != nil {
return
}
}
return createdv4 || createdv6, nil
}
func (tkr *Tracker) updatePeer(ann *models.Announce, peer *models.Peer) (created bool, err error) {
p, t := ann.Peer, ann.Torrent
switch {
case t.Seeders.Contains(p.Key()):
err = tkr.PutSeeder(t.Infohash, p)
if err != nil {
return
}
case t.Leechers.Contains(p.Key()):
err = tkr.PutLeecher(t.Infohash, p)
if err != nil {
return
}
default:
if ann.Left == 0 {
err = tkr.PutSeeder(t.Infohash, p)
if err != nil {
return
}
stats.RecordPeerEvent(stats.NewSeed, p.HasIPv6())
} else {
err = tkr.PutLeecher(t.Infohash, p)
if err != nil {
return
}
stats.RecordPeerEvent(stats.NewLeech, p.HasIPv6())
}
created = true
}
return
}
// handleEvent checks to see whether an announce has an event and if it does,
// properly handles that event.
func (tkr *Tracker) handleEvent(ann *models.Announce) (snatched bool, err error) {
var snatchedv4, snatchedv6 bool
if ann.HasIPv4() {
snatchedv4, err = tkr.handlePeerEvent(ann, ann.PeerV4)
if err != nil {
return
}
}
if ann.HasIPv6() {
snatchedv6, err = tkr.handlePeerEvent(ann, ann.PeerV6)
if err != nil {
return
}
}
if snatchedv4 || snatchedv6 {
err = tkr.IncrementTorrentSnatches(ann.Torrent.Infohash)
if err != nil {
return
}
ann.Torrent.Snatches++
return true, nil
}
return false, nil
}
func (tkr *Tracker) handlePeerEvent(ann *models.Announce, p *models.Peer) (snatched bool, err error) {
p, t := ann.Peer, ann.Torrent
switch {
case ann.Event == "stopped" || ann.Event == "paused":
// updateSwarm checks if the peer is active on the torrent,
// so one of these branches must be followed.
if t.Seeders.Contains(p.Key()) {
err = tkr.DeleteSeeder(t.Infohash, p)
if err != nil {
return
}
stats.RecordPeerEvent(stats.DeletedSeed, p.HasIPv6())
} else if t.Leechers.Contains(p.Key()) {
err = tkr.DeleteLeecher(t.Infohash, p)
if err != nil {
return
}
stats.RecordPeerEvent(stats.DeletedLeech, p.HasIPv6())
}
case t.Leechers.Contains(p.Key()) && (ann.Event == "completed" || ann.Left == 0):
// A leecher has completed or this is the first time we've seen them since
// they've completed.
err = tkr.leecherFinished(t, p)
if err != nil {
return
}
// Only mark as snatched if we receive the completed event.
if ann.Event == "completed" {
snatched = true
}
}
return
}
// leecherFinished moves a peer from the leeching pool to the seeder pool.
func (tkr *Tracker) leecherFinished(t *models.Torrent, p *models.Peer) error {
if err := tkr.DeleteLeecher(t.Infohash, p); err != nil {
return err
}
if err := tkr.PutSeeder(t.Infohash, p); err != nil {
return err
}
stats.RecordPeerEvent(stats.Completed, p.HasIPv6())
return nil
}
func newAnnounceResponse(ann *models.Announce) *models.AnnounceResponse {
seedCount := ann.Torrent.Seeders.Len()
leechCount := ann.Torrent.Leechers.Len()
res := &models.AnnounceResponse{
Announce: ann,
Complete: seedCount,
Incomplete: leechCount,
Interval: ann.Config.Announce.Duration,
MinInterval: ann.Config.MinAnnounce.Duration,
Compact: ann.Compact,
}
if ann.NumWant > 0 && ann.Event != "stopped" && ann.Event != "paused" {
res.IPv4Peers, res.IPv6Peers = getPeers(ann)
if len(res.IPv4Peers)+len(res.IPv6Peers) == 0 {
models.AppendPeer(&res.IPv4Peers, &res.IPv6Peers, ann, ann.Peer)
}
}
return res
}
// getPeers returns lists IPv4 and IPv6 peers on a given torrent sized according
// to the wanted parameter.
func getPeers(ann *models.Announce) (ipv4s, ipv6s models.PeerList) {
ipv4s, ipv6s = models.PeerList{}, models.PeerList{}
if ann.Left == 0 {
// If they're seeding, give them only leechers.
return ann.Torrent.Leechers.AppendPeers(ipv4s, ipv6s, ann, ann.NumWant)
}
// If they're leeching, prioritize giving them seeders.
ipv4s, ipv6s = ann.Torrent.Seeders.AppendPeers(ipv4s, ipv6s, ann, ann.NumWant)
return ann.Torrent.Leechers.AppendPeers(ipv4s, ipv6s, ann, ann.NumWant-len(ipv4s)-len(ipv6s))
}