mirror of
https://github.com/sot-tech/mochi.git
synced 2026-04-28 00:20:01 -07:00
301 lines
7.6 KiB
Go
301 lines
7.6 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
|
|
}
|
|
}
|
|
|
|
var user *models.User
|
|
if tkr.Config.PrivateEnabled {
|
|
if user, err = tkr.FindUser(ann.Passkey); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
torrent, err := tkr.FindTorrent(ann.Infohash)
|
|
|
|
if err == models.ErrTorrentDNE && !tkr.Config.PrivateEnabled {
|
|
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(user, torrent)
|
|
var delta *models.AnnounceDelta
|
|
|
|
if tkr.Config.PrivateEnabled {
|
|
delta = newAnnounceDelta(ann, torrent)
|
|
}
|
|
|
|
created, err := tkr.updateSwarm(ann)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
snatched, err := tkr.handleEvent(ann)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if tkr.Config.PrivateEnabled {
|
|
delta.Created = created
|
|
delta.Snatched = snatched
|
|
if err = tkr.Backend.RecordAnnounce(delta); err != nil {
|
|
return err
|
|
}
|
|
} else 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)
|
|
}
|
|
|
|
return w.WriteAnnounce(newAnnounceResponse(ann))
|
|
}
|
|
|
|
// Builds a partially populated AnnounceDelta, without the Snatched and Created
|
|
// fields set.
|
|
func newAnnounceDelta(ann *models.Announce, t *models.Torrent) *models.AnnounceDelta {
|
|
var oldUp, oldDown, rawDeltaUp, rawDeltaDown uint64
|
|
|
|
switch {
|
|
case t.Seeders.Contains(ann.Peer.Key()):
|
|
oldPeer, _ := t.Seeders.LookUp(ann.Peer.Key())
|
|
oldUp = oldPeer.Uploaded
|
|
oldDown = oldPeer.Downloaded
|
|
case t.Leechers.Contains(ann.Peer.Key()):
|
|
oldPeer, _ := t.Leechers.LookUp(ann.Peer.Key())
|
|
oldUp = oldPeer.Uploaded
|
|
oldDown = oldPeer.Downloaded
|
|
}
|
|
|
|
// Restarting a torrent may cause a delta to be negative.
|
|
if ann.Peer.Uploaded > oldUp {
|
|
rawDeltaUp = ann.Peer.Uploaded - oldUp
|
|
}
|
|
if ann.Peer.Downloaded > oldDown {
|
|
rawDeltaDown = ann.Peer.Downloaded - oldDown
|
|
}
|
|
|
|
uploaded := uint64(float64(rawDeltaUp) * ann.User.UpMultiplier * ann.Torrent.UpMultiplier)
|
|
downloaded := uint64(float64(rawDeltaDown) * ann.User.DownMultiplier * ann.Torrent.DownMultiplier)
|
|
|
|
if ann.Config.FreeleechEnabled {
|
|
downloaded = 0
|
|
}
|
|
|
|
return &models.AnnounceDelta{
|
|
Peer: ann.Peer,
|
|
Torrent: ann.Torrent,
|
|
User: ann.User,
|
|
|
|
Uploaded: uploaded,
|
|
RawUploaded: rawDeltaUp,
|
|
Downloaded: downloaded,
|
|
RawDownloaded: rawDeltaDown,
|
|
}
|
|
}
|
|
|
|
// 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.Event != "" && ann.Event != "started" {
|
|
err = models.ErrBadRequest
|
|
return
|
|
}
|
|
|
|
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 ann.Event == "completed":
|
|
v4seed := t.Seeders.Contains(p.Key())
|
|
v6seed := t.Seeders.Contains(p.Key())
|
|
|
|
if t.Leechers.Contains(p.Key()) {
|
|
err = tkr.leecherFinished(t, p)
|
|
} else {
|
|
err = models.ErrBadRequest
|
|
}
|
|
|
|
// If one of the dual-stacked peers is already a seeder, they have
|
|
// already snatched.
|
|
if !(v4seed || v6seed) {
|
|
snatched = true
|
|
}
|
|
|
|
case t.Leechers.Contains(p.Key()) && ann.Left == 0:
|
|
// A leecher completed but the event was never received.
|
|
err = tkr.leecherFinished(t, p)
|
|
}
|
|
|
|
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{
|
|
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)
|
|
}
|
|
|
|
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))
|
|
}
|