tracker/models/models.go

266 lines
6.7 KiB
Go
Raw Normal View History

2014-06-24 04:47:43 +02:00
// Copyright 2013 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 models implements the models for an abstraction over the
// multiple data stores used by a BitTorrent tracker.
package models
import (
"errors"
"net/http"
"path"
"strconv"
"time"
"github.com/chihaya/chihaya/config"
"github.com/chihaya/chihaya/models/query"
)
var (
// ErrMalformedRequest is returned when a request does no have the required
// parameters.
ErrMalformedRequest = errors.New("malformed request")
)
// Peer is the internal representation of a participant in a swarm.
type Peer struct {
ID string `json:"id"`
UserID uint64 `json:"user_id"`
TorrentID uint64 `json:"torrent_id"`
IP string `json:"ip"`
Port uint64 `json:"port"`
Uploaded uint64 `json:"uploaded"`
Downloaded uint64 `json:"downloaded`
Left uint64 `json:"left"`
LastAnnounce int64 `json:"last_announce"`
}
// Key is a helper that returns the proper format for keys used for maps
// of peers (i.e. torrent.Seeders & torrent.Leechers).
func (p Peer) Key() string {
return p.ID + ":" + strconv.FormatUint(p.UserID, 36)
}
// Torrent is the internal representation of a swarm for a given torrent file.
type Torrent struct {
ID uint64 `json:"id"`
Infohash string `json:"infohash"`
Active bool `json:"active"`
Seeders map[string]Peer `json:"seeders"`
Leechers map[string]Peer `json:"leechers"`
Snatches uint64 `json:"snatches"`
UpMultiplier float64 `json:"up_multiplier"`
DownMultiplier float64 `json:"down_multiplier"`
LastAction int64 `json:"last_action"`
}
// InSeederPool returns true if a peer is within a torrent's pool of seeders.
func (t *Torrent) InSeederPool(p *Peer) bool {
_, exists := t.Seeders[p.Key()]
return exists
}
// InLeecherPool returns true if a peer is within a torrent's pool of leechers.
func (t *Torrent) InLeecherPool(p *Peer) bool {
_, exists := t.Leechers[p.Key()]
return exists
}
// NewPeer creates a new peer using the information provided by an announce.
func NewPeer(t *Torrent, u *User, a *Announce) *Peer {
return &Peer{
ID: a.PeerID,
UserID: u.ID,
TorrentID: t.ID,
IP: a.IP,
Port: a.Port,
Uploaded: a.Uploaded,
Downloaded: a.Downloaded,
Left: a.Left,
LastAnnounce: time.Now().Unix(),
}
}
// User is the internal representation of registered user for private trackers.
type User struct {
ID uint64 `json:"id"`
Passkey string `json:"passkey"`
UpMultiplier float64 `json:"up_multiplier"`
DownMultiplier float64 `json:"down_multiplier"`
Snatches uint64 `json:"snatches"`
}
// Announce represents all of the data from an announce request.
type Announce struct {
Config *config.Config `json:"config"`
Request *http.Request `json:"request"`
Compact bool `json:"compact"`
Downloaded uint64 `json:"downloaded"`
Event string `json:"event"`
IP string `json:"ip"`
Infohash string `json:"infohash"`
Left uint64 `json:"left"`
NumWant int `json:"numwant"`
Passkey string `json:"passkey"`
PeerID string `json:"peer_id"`
Port uint64 `json:"port"`
Uploaded uint64 `json:"uploaded"`
}
// NewAnnounce parses an HTTP request and generates an Announce.
func NewAnnounce(r *http.Request, conf *config.Config) (*Announce, error) {
q, err := query.New(r.URL.RawQuery)
if err != nil {
return nil, err
}
compact := q.Params["compact"] != "0"
downloaded, downloadedErr := q.Uint64("downloaded")
event, _ := q.Params["event"]
infohash, _ := q.Params["info_hash"]
ip, _ := q.RequestedIP(r)
left, leftErr := q.Uint64("left")
numWant := q.RequestedPeerCount(conf.DefaultNumWant)
dir, _ := path.Split(r.URL.Path)
peerID, _ := q.Params["peer_id"]
port, portErr := q.Uint64("port")
uploaded, uploadedErr := q.Uint64("uploaded")
if downloadedErr != nil ||
infohash == "" ||
leftErr != nil ||
peerID == "" ||
portErr != nil ||
uploadedErr != nil ||
ip == "" ||
len(dir) != 34 {
return nil, ErrMalformedRequest
}
return &Announce{
Config: conf,
Request: r,
Compact: compact,
Downloaded: downloaded,
Event: event,
IP: ip,
Infohash: infohash,
Left: left,
NumWant: numWant,
Passkey: dir[1:33],
PeerID: peerID,
Port: port,
Uploaded: uploaded,
}, nil
}
// ClientID returns the part of a PeerID that identifies the client software.
func (a Announce) ClientID() (clientID string) {
length := len(a.PeerID)
if length >= 6 {
if a.PeerID[0] == '-' {
if length >= 7 {
clientID = a.PeerID[1:7]
}
} else {
clientID = a.PeerID[0:6]
}
}
return
}
// AnnounceDelta contains a difference in statistics for a peer.
// It is used for communicating changes to be recorded by the driver.
type AnnounceDelta struct {
Peer *Peer
Torrent *Torrent
User *User
// Created is true if this announce created a new peer or changed an existing
// peer's address
Created bool
// Snatched is true if this announce completed the download
Snatched bool
// Uploaded contains the raw upload delta for this announce, in bytes
Uploaded uint64
// Downloaded contains the raw download delta for this announce, in bytes
Downloaded uint64
}
// NewAnnounceDelta does stuff
func NewAnnounceDelta(p *Peer, u *User, a *Announce, t *Torrent, created, snatched bool) *AnnounceDelta {
rawDeltaUp := p.Uploaded - a.Uploaded
rawDeltaDown := p.Downloaded - a.Downloaded
// Restarting a torrent may cause a delta to be negative.
if rawDeltaUp < 0 {
rawDeltaUp = 0
}
if rawDeltaDown < 0 {
rawDeltaDown = 0
}
return &AnnounceDelta{
Peer: p,
Torrent: t,
User: u,
Created: created,
Snatched: snatched,
Uploaded: uint64(float64(rawDeltaUp) * u.UpMultiplier * t.UpMultiplier),
Downloaded: uint64(float64(rawDeltaDown) * u.DownMultiplier * t.DownMultiplier),
}
}
// Scrape represents all of the data from an scrape request.
type Scrape struct {
Config *config.Config `json:"config"`
Request *http.Request `json:"request"`
Passkey string
Infohashes []string
}
// NewScrape parses an HTTP request and generates a Scrape.
func NewScrape(r *http.Request, c *config.Config) (*Scrape, error) {
q, err := query.New(r.URL.RawQuery)
if err != nil {
return nil, err
}
var passkey string
if c.Private {
dir, _ := path.Split(r.URL.Path)
if len(dir) != 34 {
return nil, ErrMalformedRequest
}
passkey = dir[1:34]
}
if q.Infohashes == nil {
if _, exists := q.Params["infohash"]; !exists {
// There aren't any infohashes.
return nil, ErrMalformedRequest
}
q.Infohashes = []string{q.Params["infohash"]}
}
return &Scrape{
Config: c,
Request: r,
Passkey: passkey,
Infohashes: q.Infohashes,
}, nil
}