mirror of
https://github.com/sot-tech/mochi.git
synced 2026-07-13 12:08:11 -07:00
First step towards removing Tracker Driver.
This feature isn't worth maintaining and if anyone needs to scale beyond memory on a single box, we can evaluate it then.
This commit is contained in:
+29
-39
@@ -11,28 +11,21 @@ import (
|
||||
|
||||
// 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) error {
|
||||
conn, err := tkr.Pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
func (tkr *Tracker) HandleAnnounce(ann *models.Announce, w Writer) (err error) {
|
||||
if tkr.cfg.ClientWhitelistEnabled {
|
||||
if err = conn.FindClient(ann.ClientID()); err != nil {
|
||||
if err = tkr.ClientApproved(ann.ClientID()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
if tkr.cfg.PrivateEnabled {
|
||||
if user, err = conn.FindUser(ann.Passkey); err != nil {
|
||||
if user, err = tkr.FindUser(ann.Passkey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
torrent, err := conn.FindTorrent(ann.Infohash)
|
||||
torrent, err := tkr.FindTorrent(ann.Infohash)
|
||||
|
||||
if err == models.ErrTorrentDNE && !tkr.cfg.PrivateEnabled {
|
||||
torrent = &models.Torrent{
|
||||
@@ -41,10 +34,7 @@ func (tkr *Tracker) HandleAnnounce(ann *models.Announce, w Writer) error {
|
||||
Leechers: models.NewPeerMap(false),
|
||||
}
|
||||
|
||||
err = conn.PutTorrent(torrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tkr.PutTorrent(torrent)
|
||||
stats.RecordEvent(stats.NewTorrent)
|
||||
} else if err != nil {
|
||||
return err
|
||||
@@ -57,12 +47,12 @@ func (tkr *Tracker) HandleAnnounce(ann *models.Announce, w Writer) error {
|
||||
delta = newAnnounceDelta(ann, torrent)
|
||||
}
|
||||
|
||||
created, err := updateSwarm(conn, ann)
|
||||
created, err := tkr.updateSwarm(ann)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
snatched, err := handleEvent(conn, ann)
|
||||
snatched, err := tkr.handleEvent(ann)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,7 +66,7 @@ func (tkr *Tracker) HandleAnnounce(ann *models.Announce, w Writer) error {
|
||||
} else if tkr.cfg.PurgeInactiveTorrents && torrent.PeerCount() == 0 {
|
||||
// Rather than deleting the torrent explicitly, let the tracker driver
|
||||
// ensure there are no race conditions.
|
||||
conn.PurgeInactiveTorrent(torrent.Infohash)
|
||||
tkr.PurgeInactiveTorrent(torrent.Infohash)
|
||||
stats.RecordEvent(stats.DeletedTorrent)
|
||||
}
|
||||
|
||||
@@ -127,18 +117,18 @@ func newAnnounceDelta(ann *models.Announce, t *models.Torrent) *models.AnnounceD
|
||||
}
|
||||
|
||||
// updateSwarm handles the changes to a torrent's swarm given an announce.
|
||||
func updateSwarm(c Conn, ann *models.Announce) (created bool, err error) {
|
||||
func (tkr *Tracker) updateSwarm(ann *models.Announce) (created bool, err error) {
|
||||
var createdv4, createdv6 bool
|
||||
c.TouchTorrent(ann.Torrent.Infohash)
|
||||
tkr.TouchTorrent(ann.Torrent.Infohash)
|
||||
|
||||
if ann.HasIPv4() {
|
||||
createdv4, err = updatePeer(c, ann, ann.PeerV4)
|
||||
createdv4, err = tkr.updatePeer(ann, ann.PeerV4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ann.HasIPv6() {
|
||||
createdv6, err = updatePeer(c, ann, ann.PeerV6)
|
||||
createdv6, err = tkr.updatePeer(ann, ann.PeerV6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -147,18 +137,18 @@ func updateSwarm(c Conn, ann *models.Announce) (created bool, err error) {
|
||||
return createdv4 || createdv6, nil
|
||||
}
|
||||
|
||||
func updatePeer(c Conn, ann *models.Announce, peer *models.Peer) (created bool, err error) {
|
||||
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 = c.PutSeeder(t.Infohash, p)
|
||||
err = tkr.PutSeeder(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case t.Leechers.Contains(p.Key()):
|
||||
err = c.PutLeecher(t.Infohash, p)
|
||||
err = tkr.PutLeecher(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -170,14 +160,14 @@ func updatePeer(c Conn, ann *models.Announce, peer *models.Peer) (created bool,
|
||||
}
|
||||
|
||||
if ann.Left == 0 {
|
||||
err = c.PutSeeder(t.Infohash, p)
|
||||
err = tkr.PutSeeder(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stats.RecordPeerEvent(stats.NewSeed, p.HasIPv6())
|
||||
|
||||
} else {
|
||||
err = c.PutLeecher(t.Infohash, p)
|
||||
err = tkr.PutLeecher(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -190,24 +180,24 @@ func updatePeer(c Conn, ann *models.Announce, peer *models.Peer) (created bool,
|
||||
|
||||
// handleEvent checks to see whether an announce has an event and if it does,
|
||||
// properly handles that event.
|
||||
func handleEvent(c Conn, ann *models.Announce) (snatched bool, err error) {
|
||||
func (tkr *Tracker) handleEvent(ann *models.Announce) (snatched bool, err error) {
|
||||
var snatchedv4, snatchedv6 bool
|
||||
|
||||
if ann.HasIPv4() {
|
||||
snatchedv4, err = handlePeerEvent(c, ann, ann.PeerV4)
|
||||
snatchedv4, err = tkr.handlePeerEvent(ann, ann.PeerV4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ann.HasIPv6() {
|
||||
snatchedv6, err = handlePeerEvent(c, ann, ann.PeerV6)
|
||||
snatchedv6, err = tkr.handlePeerEvent(ann, ann.PeerV6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if snatchedv4 || snatchedv6 {
|
||||
err = c.IncrementTorrentSnatches(ann.Torrent.Infohash)
|
||||
err = tkr.IncrementTorrentSnatches(ann.Torrent.Infohash)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -217,7 +207,7 @@ func handleEvent(c Conn, ann *models.Announce) (snatched bool, err error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func handlePeerEvent(c Conn, ann *models.Announce, p *models.Peer) (snatched bool, err error) {
|
||||
func (tkr *Tracker) handlePeerEvent(ann *models.Announce, p *models.Peer) (snatched bool, err error) {
|
||||
p, t := ann.Peer, ann.Torrent
|
||||
|
||||
switch {
|
||||
@@ -225,14 +215,14 @@ func handlePeerEvent(c Conn, ann *models.Announce, p *models.Peer) (snatched boo
|
||||
// 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 = c.DeleteSeeder(t.Infohash, p)
|
||||
err = tkr.DeleteSeeder(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stats.RecordPeerEvent(stats.DeletedSeed, p.HasIPv6())
|
||||
|
||||
} else if t.Leechers.Contains(p.Key()) {
|
||||
err = c.DeleteLeecher(t.Infohash, p)
|
||||
err = tkr.DeleteLeecher(t.Infohash, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -244,7 +234,7 @@ func handlePeerEvent(c Conn, ann *models.Announce, p *models.Peer) (snatched boo
|
||||
v6seed := t.Seeders.Contains(models.NewPeerKey(p.ID, true))
|
||||
|
||||
if t.Leechers.Contains(p.Key()) {
|
||||
err = leecherFinished(c, t, p)
|
||||
err = tkr.leecherFinished(t, p)
|
||||
} else {
|
||||
err = models.ErrBadRequest
|
||||
}
|
||||
@@ -257,18 +247,18 @@ func handlePeerEvent(c Conn, ann *models.Announce, p *models.Peer) (snatched boo
|
||||
|
||||
case t.Leechers.Contains(p.Key()) && ann.Left == 0:
|
||||
// A leecher completed but the event was never received.
|
||||
err = leecherFinished(c, t, p)
|
||||
err = tkr.leecherFinished(t, p)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// leecherFinished moves a peer from the leeching pool to the seeder pool.
|
||||
func leecherFinished(c Conn, t *models.Torrent, p *models.Peer) error {
|
||||
if err := c.DeleteLeecher(t.Infohash, p); err != nil {
|
||||
func (tkr *Tracker) leecherFinished(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tkr.DeleteLeecher(t.Infohash, p); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.PutSeeder(t.Infohash, p); err != nil {
|
||||
if err := tkr.PutSeeder(t.Infohash, p); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.RecordPeerEvent(stats.Completed, p.HasIPv6())
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2014 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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/chihaya/chihaya/config"
|
||||
"github.com/chihaya/chihaya/tracker/models"
|
||||
)
|
||||
|
||||
var drivers = make(map[string]Driver)
|
||||
|
||||
// Driver represents an interface to pool of connections to models used for
|
||||
// the tracker.
|
||||
type Driver interface {
|
||||
New(*config.DriverConfig) Pool
|
||||
}
|
||||
|
||||
// Register makes a database driver available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, driver Driver) {
|
||||
if driver == nil {
|
||||
panic("tracker: Register driver is nil")
|
||||
}
|
||||
if _, dup := drivers[name]; dup {
|
||||
panic("tracker: Register called twice for driver " + name)
|
||||
}
|
||||
drivers[name] = driver
|
||||
}
|
||||
|
||||
// Open creates a pool of data store connections specified by a configuration.
|
||||
func Open(cfg *config.DriverConfig) (Pool, error) {
|
||||
driver, ok := drivers[cfg.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"unknown driver %q (forgotten import?)",
|
||||
cfg.Name,
|
||||
)
|
||||
}
|
||||
return driver.New(cfg), nil
|
||||
}
|
||||
|
||||
// Pool represents a thread-safe pool of connections to the data store
|
||||
// that can be used to safely within concurrent goroutines.
|
||||
type Pool interface {
|
||||
Close() error
|
||||
Get() (Conn, error)
|
||||
}
|
||||
|
||||
// Conn represents a connection to the data store that can be used
|
||||
// to make reads/writes.
|
||||
type Conn interface {
|
||||
Close() error
|
||||
|
||||
// Torrent interactions
|
||||
TouchTorrent(infohash string) error
|
||||
FindTorrent(infohash string) (*models.Torrent, error)
|
||||
PutTorrent(t *models.Torrent) error
|
||||
DeleteTorrent(infohash string) error
|
||||
IncrementTorrentSnatches(infohash string) error
|
||||
|
||||
PutLeecher(infohash string, p *models.Peer) error
|
||||
DeleteLeecher(infohash string, p *models.Peer) error
|
||||
|
||||
PutSeeder(infohash string, p *models.Peer) error
|
||||
DeleteSeeder(infohash string, p *models.Peer) error
|
||||
|
||||
PurgeInactiveTorrent(infohash string) error
|
||||
PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) error
|
||||
|
||||
// User interactions
|
||||
FindUser(passkey string) (*models.User, error)
|
||||
PutUser(u *models.User) error
|
||||
DeleteUser(passkey string) error
|
||||
|
||||
// Whitelist interactions
|
||||
FindClient(clientID string) error
|
||||
PutClient(clientID string) error
|
||||
DeleteClient(clientID string) error
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
// Copyright 2014 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 memory
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/chihaya/chihaya/stats"
|
||||
"github.com/chihaya/chihaya/tracker/models"
|
||||
)
|
||||
|
||||
// Conn implements a connection to a memory-based tracker data store.
|
||||
type Conn struct {
|
||||
*Pool
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) FindUser(passkey string) (*models.User, error) {
|
||||
c.usersM.RLock()
|
||||
defer c.usersM.RUnlock()
|
||||
|
||||
user, exists := c.users[passkey]
|
||||
if !exists {
|
||||
return nil, models.ErrUserDNE
|
||||
}
|
||||
return &*user, nil
|
||||
}
|
||||
|
||||
func (c *Conn) FindTorrent(infohash string) (*models.Torrent, error) {
|
||||
c.torrentsM.RLock()
|
||||
defer c.torrentsM.RUnlock()
|
||||
|
||||
torrent, exists := c.torrents[infohash]
|
||||
if !exists {
|
||||
return nil, models.ErrTorrentDNE
|
||||
}
|
||||
return &*torrent, nil
|
||||
}
|
||||
|
||||
func (c *Conn) FindClient(peerID string) error {
|
||||
c.whitelistM.RLock()
|
||||
defer c.whitelistM.RUnlock()
|
||||
|
||||
_, ok := c.whitelist[peerID]
|
||||
if !ok {
|
||||
return models.ErrClientUnapproved
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) IncrementTorrentSnatches(infohash string) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, exists := c.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.Snatches++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) TouchTorrent(infohash string) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, ok := c.torrents[infohash]
|
||||
if !ok {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.LastAction = time.Now().Unix()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) DeleteLeecher(infohash string, p *models.Peer) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, ok := c.torrents[infohash]
|
||||
if !ok {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.Leechers.Delete(p.Key())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) DeleteSeeder(infohash string, p *models.Peer) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, ok := c.torrents[infohash]
|
||||
if !ok {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.Seeders.Delete(p.Key())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PutLeecher(infohash string, p *models.Peer) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, ok := c.torrents[infohash]
|
||||
if !ok {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.Leechers.Put(*p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PutSeeder(infohash string, p *models.Peer) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
t, ok := c.torrents[infohash]
|
||||
if !ok {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
t.Seeders.Put(*p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PutTorrent(t *models.Torrent) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
c.torrents[t.Infohash] = &*t
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) DeleteTorrent(infohash string) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
delete(c.torrents, infohash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PurgeInactiveTorrent(infohash string) error {
|
||||
c.torrentsM.Lock()
|
||||
defer c.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := c.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
if torrent.PeerCount() == 0 {
|
||||
delete(c.torrents, infohash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PutUser(u *models.User) error {
|
||||
c.usersM.Lock()
|
||||
defer c.usersM.Unlock()
|
||||
|
||||
c.users[u.Passkey] = &*u
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) DeleteUser(passkey string) error {
|
||||
c.usersM.Lock()
|
||||
defer c.usersM.Unlock()
|
||||
|
||||
delete(c.users, passkey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PutClient(peerID string) error {
|
||||
c.whitelistM.Lock()
|
||||
defer c.whitelistM.Unlock()
|
||||
|
||||
c.whitelist[peerID] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) DeleteClient(peerID string) error {
|
||||
c.whitelistM.Lock()
|
||||
defer c.whitelistM.Unlock()
|
||||
|
||||
delete(c.whitelist, peerID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) error {
|
||||
unixtime := before.Unix()
|
||||
|
||||
// Build array of map keys to operate on.
|
||||
c.torrentsM.RLock()
|
||||
index := 0
|
||||
keys := make([]string, len(c.torrents))
|
||||
|
||||
for infohash := range c.torrents {
|
||||
keys[index] = infohash
|
||||
index++
|
||||
}
|
||||
|
||||
c.torrentsM.RUnlock()
|
||||
|
||||
// Process keys.
|
||||
for _, infohash := range keys {
|
||||
runtime.Gosched() // Let other goroutines run, since this is low priority.
|
||||
|
||||
c.torrentsM.Lock()
|
||||
torrent := c.torrents[infohash]
|
||||
|
||||
if torrent == nil {
|
||||
continue // Torrent deleted since keys were computed.
|
||||
}
|
||||
|
||||
torrent.Seeders.Purge(unixtime)
|
||||
torrent.Leechers.Purge(unixtime)
|
||||
|
||||
peers := torrent.PeerCount()
|
||||
c.torrentsM.Unlock()
|
||||
|
||||
if purgeEmptyTorrents && peers == 0 {
|
||||
c.PurgeInactiveTorrent(infohash)
|
||||
stats.RecordEvent(stats.ReapedTorrent)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2014 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 memory implements a Chihaya tracker storage driver within memory.
|
||||
// Stored values will not persist if the tracker is restarted.
|
||||
package memory
|
||||
|
||||
import (
|
||||
"github.com/chihaya/chihaya/config"
|
||||
"github.com/chihaya/chihaya/tracker"
|
||||
"github.com/chihaya/chihaya/tracker/models"
|
||||
)
|
||||
|
||||
type driver struct{}
|
||||
|
||||
func (d *driver) New(cfg *config.DriverConfig) tracker.Pool {
|
||||
return &Pool{
|
||||
users: make(map[string]*models.User),
|
||||
torrents: make(map[string]*models.Torrent),
|
||||
whitelist: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
tracker.Register("memory", &driver{})
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2014 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 memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/chihaya/chihaya/tracker"
|
||||
"github.com/chihaya/chihaya/tracker/models"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
users map[string]*models.User
|
||||
usersM sync.RWMutex
|
||||
|
||||
torrents map[string]*models.Torrent
|
||||
torrentsM sync.RWMutex
|
||||
|
||||
whitelist map[string]bool
|
||||
whitelistM sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *Pool) Get() (tracker.Conn, error) {
|
||||
return &Conn{
|
||||
Pool: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Pool) Close() error {
|
||||
return nil
|
||||
}
|
||||
+2
-9
@@ -9,22 +9,15 @@ import "github.com/chihaya/chihaya/tracker/models"
|
||||
// HandleScrape encapsulates all the logic of handling a BitTorrent client's
|
||||
// scrape without being coupled to any transport protocol.
|
||||
func (tkr *Tracker) HandleScrape(scrape *models.Scrape, w Writer) (err error) {
|
||||
conn, err := tkr.Pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
if tkr.cfg.PrivateEnabled {
|
||||
if _, err = conn.FindUser(scrape.Passkey); err != nil {
|
||||
if _, err = tkr.FindUser(scrape.Passkey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var torrents []*models.Torrent
|
||||
for _, infohash := range scrape.Infohashes {
|
||||
torrent, err := conn.FindTorrent(infohash)
|
||||
torrent, err := tkr.FindTorrent(infohash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/chihaya/chihaya/stats"
|
||||
"github.com/chihaya/chihaya/tracker/models"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
users map[string]*models.User
|
||||
usersM sync.RWMutex
|
||||
|
||||
torrents map[string]*models.Torrent
|
||||
torrentsM sync.RWMutex
|
||||
|
||||
clients map[string]bool
|
||||
clientsM sync.RWMutex
|
||||
}
|
||||
|
||||
func NewStorage() *Storage {
|
||||
return &Storage{
|
||||
users: make(map[string]*models.User),
|
||||
torrents: make(map[string]*models.Torrent),
|
||||
clients: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) TouchTorrent(infohash string) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.LastAction = time.Now().Unix()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) FindTorrent(infohash string) (*models.Torrent, error) {
|
||||
s.torrentsM.RLock()
|
||||
defer s.torrentsM.RUnlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return nil, models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
return &*torrent, nil
|
||||
}
|
||||
|
||||
func (s *Storage) PutTorrent(torrent *models.Torrent) {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
s.torrents[torrent.Infohash] = &*torrent
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteTorrent(infohash string) {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
delete(s.torrents, infohash)
|
||||
}
|
||||
|
||||
func (s *Storage) IncrementTorrentSnatches(infohash string) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.Snatches++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) PutLeecher(infohash string, p *models.Peer) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.Leechers.Put(*p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteLeecher(infohash string, p *models.Peer) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.Leechers.Delete(p.Key())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) PutSeeder(infohash string, p *models.Peer) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.Seeders.Put(*p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteSeeder(infohash string, p *models.Peer) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
torrent.Seeders.Delete(p.Key())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) PurgeInactiveTorrent(infohash string) error {
|
||||
s.torrentsM.Lock()
|
||||
defer s.torrentsM.Unlock()
|
||||
|
||||
torrent, exists := s.torrents[infohash]
|
||||
if !exists {
|
||||
return models.ErrTorrentDNE
|
||||
}
|
||||
|
||||
if torrent.PeerCount() == 0 {
|
||||
delete(s.torrents, infohash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) error {
|
||||
unixtime := before.Unix()
|
||||
|
||||
// Build a list of keys to process.
|
||||
s.torrentsM.RLock()
|
||||
index := 0
|
||||
keys := make([]string, len(s.torrents))
|
||||
|
||||
for infohash := range s.torrents {
|
||||
keys[index] = infohash
|
||||
index++
|
||||
}
|
||||
s.torrentsM.RUnlock()
|
||||
|
||||
// Process the keys while allowing other goroutines to run.
|
||||
for _, infohash := range keys {
|
||||
runtime.Gosched()
|
||||
|
||||
s.torrentsM.Lock()
|
||||
torrent := s.torrents[infohash]
|
||||
|
||||
if torrent == nil {
|
||||
// The torrent has already been deleted since keys were computed.
|
||||
continue
|
||||
}
|
||||
|
||||
torrent.Seeders.Purge(unixtime)
|
||||
torrent.Leechers.Purge(unixtime)
|
||||
|
||||
peers := torrent.PeerCount()
|
||||
s.torrentsM.Unlock()
|
||||
|
||||
if purgeEmptyTorrents && peers == 0 {
|
||||
s.PurgeInactiveTorrent(infohash)
|
||||
stats.RecordEvent(stats.ReapedTorrent)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) FindUser(passkey string) (*models.User, error) {
|
||||
s.usersM.RLock()
|
||||
defer s.usersM.RUnlock()
|
||||
|
||||
user, exists := s.users[passkey]
|
||||
if !exists {
|
||||
return nil, models.ErrUserDNE
|
||||
}
|
||||
|
||||
return &*user, nil
|
||||
}
|
||||
|
||||
func (s *Storage) PutUser(user *models.User) {
|
||||
s.usersM.Lock()
|
||||
defer s.usersM.Unlock()
|
||||
|
||||
s.users[user.Passkey] = &*user
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteUser(passkey string) {
|
||||
s.usersM.Lock()
|
||||
defer s.usersM.Unlock()
|
||||
|
||||
delete(s.users, passkey)
|
||||
}
|
||||
|
||||
func (s *Storage) ClientApproved(peerID string) error {
|
||||
s.clientsM.RLock()
|
||||
defer s.clientsM.RUnlock()
|
||||
|
||||
_, exists := s.clients[peerID]
|
||||
if !exists {
|
||||
return models.ErrClientUnapproved
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) PutClient(peerID string) {
|
||||
s.clientsM.Lock()
|
||||
defer s.clientsM.Unlock()
|
||||
|
||||
s.clients[peerID] = true
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteClient(peerID string) {
|
||||
s.clientsM.Lock()
|
||||
defer s.clientsM.Unlock()
|
||||
|
||||
delete(s.clients, peerID)
|
||||
}
|
||||
+16
-53
@@ -20,76 +20,48 @@ import (
|
||||
// independently of the underlying data transports used.
|
||||
type Tracker struct {
|
||||
cfg *config.Config
|
||||
Pool Pool
|
||||
backend backend.Conn
|
||||
|
||||
*Storage
|
||||
}
|
||||
|
||||
// New creates a new Tracker, and opens any necessary connections.
|
||||
// Maintenance routines are automatically spawned in the background.
|
||||
func New(cfg *config.Config) (*Tracker, error) {
|
||||
pool, err := Open(&cfg.Tracker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bc, err := backend.Open(&cfg.Backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go purgeInactivePeers(
|
||||
pool,
|
||||
tkr := &Tracker{
|
||||
cfg: cfg,
|
||||
backend: bc,
|
||||
Storage: NewStorage(),
|
||||
}
|
||||
|
||||
go tkr.purgeInactivePeers(
|
||||
cfg.PurgeInactiveTorrents,
|
||||
cfg.Announce.Duration*2,
|
||||
cfg.Announce.Duration,
|
||||
)
|
||||
|
||||
tkr := &Tracker{
|
||||
cfg: cfg,
|
||||
Pool: pool,
|
||||
backend: bc,
|
||||
}
|
||||
|
||||
if cfg.ClientWhitelistEnabled {
|
||||
err = tkr.LoadApprovedClients(cfg.ClientWhitelist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkr.LoadApprovedClients(cfg.ClientWhitelist)
|
||||
}
|
||||
|
||||
return tkr, nil
|
||||
}
|
||||
|
||||
// Close gracefully shutdowns a Tracker by closing any database connections.
|
||||
func (tkr *Tracker) Close() (err error) {
|
||||
err = tkr.Pool.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = tkr.backend.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
func (tkr *Tracker) Close() error {
|
||||
return tkr.backend.Close()
|
||||
}
|
||||
|
||||
// LoadApprovedClients loads a list of client IDs into the tracker's storage.
|
||||
func (tkr *Tracker) LoadApprovedClients(clients []string) error {
|
||||
conn, err := tkr.Pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (tkr *Tracker) LoadApprovedClients(clients []string) {
|
||||
for _, client := range clients {
|
||||
err = conn.PutClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tkr.PutClient(client)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Writer serializes a tracker's responses, and is implemented for each
|
||||
@@ -112,23 +84,14 @@ type Writer interface {
|
||||
//
|
||||
// The default interval is equal to the announce interval, since this is a
|
||||
// relatively expensive operation.
|
||||
func purgeInactivePeers(p Pool, purgeEmptyTorrents bool, threshold, interval time.Duration) {
|
||||
func (tkr *Tracker) purgeInactivePeers(purgeEmptyTorrents bool, threshold, interval time.Duration) {
|
||||
for _ = range time.NewTicker(interval).C {
|
||||
before := time.Now().Add(-threshold)
|
||||
glog.V(0).Infof("Purging peers with no announces since %s", before)
|
||||
|
||||
conn, err := p.Get()
|
||||
|
||||
if err != nil {
|
||||
glog.Error("Unable to get connection for a routine")
|
||||
continue
|
||||
}
|
||||
|
||||
err = conn.PurgeInactivePeers(purgeEmptyTorrents, before)
|
||||
err := tkr.PurgeInactivePeers(purgeEmptyTorrents, before)
|
||||
if err != nil {
|
||||
glog.Errorf("Error purging torrents: %s", err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user