mirror of
https://github.com/sot-tech/mochi.git
synced 2026-07-18 06:08:10 -07:00
initial architecture overhaul
This commit is contained in:
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
// 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 storage provides a generic interface for manipulating a
|
||||
// BitTorrent tracker's cache.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pushrax/chihaya/config"
|
||||
"github.com/pushrax/chihaya/models"
|
||||
)
|
||||
|
||||
var (
|
||||
drivers = make(map[string]Driver)
|
||||
ErrTxDone = errors.New("storage: Transaction has already been committed or rolled back")
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
New(*config.Cache) 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("storage: Register driver is nil")
|
||||
}
|
||||
if _, dup := drivers[name]; dup {
|
||||
panic("storage: Register called twice for driver " + name)
|
||||
}
|
||||
drivers[name] = driver
|
||||
}
|
||||
|
||||
// Open creates a pool of data store connections specified by a storage configuration.
|
||||
func Open(conf *config.Cache) (Pool, error) {
|
||||
driver, ok := drivers[conf.Driver]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"storage: unknown driver %q (forgotten import?)",
|
||||
conf.Driver,
|
||||
)
|
||||
}
|
||||
pool := driver.New(conf)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// Pool represents a thread-safe pool of connections to the data store
|
||||
// that can be used to obtain transactions.
|
||||
type Pool interface {
|
||||
Close() error
|
||||
Get() (Tx, error)
|
||||
}
|
||||
|
||||
// Tx represents an in-progress data store transaction.
|
||||
// A transaction must end with a call to Commit or Rollback.
|
||||
//
|
||||
// After a call to Commit or Rollback, all operations on the
|
||||
// transaction must fail with ErrTxDone.
|
||||
type Tx interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
|
||||
// Reads
|
||||
FindUser(passkey string) (*models.User, bool, error)
|
||||
FindTorrent(infohash string) (*models.Torrent, bool, error)
|
||||
ClientWhitelisted(peerID string) (bool, error)
|
||||
|
||||
// Writes
|
||||
RecordSnatch(u *models.User, t *models.Torrent) error
|
||||
MarkActive(t *models.Torrent) error
|
||||
AddLeecher(t *models.Torrent, p *models.Peer) error
|
||||
AddSeeder(t *models.Torrent, p *models.Peer) error
|
||||
RemoveLeecher(t *models.Torrent, p *models.Peer) error
|
||||
RemoveSeeder(t *models.Torrent, p *models.Peer) error
|
||||
SetLeecher(t *models.Torrent, p *models.Peer) error
|
||||
SetSeeder(t *models.Torrent, p *models.Peer) error
|
||||
IncrementSlots(u *models.User) error
|
||||
DecrementSlots(u *models.User) error
|
||||
}
|
||||
Vendored
+316
@@ -0,0 +1,316 @@
|
||||
// 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 redis implements the storage interface for a BitTorrent tracker.
|
||||
//
|
||||
// The client whitelist is represented as a set with the key name "whitelist"
|
||||
// with an optional prefix. Torrents and users are JSON-formatted strings.
|
||||
// Torrents' keys are named "torrent:<infohash>" with an optional prefix.
|
||||
// Users' keys are named "user:<passkey>" with an optional prefix.
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
|
||||
"github.com/pushrax/chihaya/cache"
|
||||
"github.com/pushrax/chihaya/config"
|
||||
"github.com/pushrax/chihaya/models"
|
||||
)
|
||||
|
||||
type driver struct{}
|
||||
|
||||
func (d *driver) New(conf *config.Cache) cache.Pool {
|
||||
return &Pool{
|
||||
conf: conf,
|
||||
pool: redis.Pool{
|
||||
MaxIdle: conf.MaxIdleConn,
|
||||
IdleTimeout: conf.IdleTimeout.Duration,
|
||||
Dial: makeDialFunc(conf),
|
||||
TestOnBorrow: testOnBorrow,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeDialFunc(conf *config.Cache) func() (redis.Conn, error) {
|
||||
return func() (conn redis.Conn, err error) {
|
||||
if conf.ConnTimeout != nil {
|
||||
conn, err = redis.DialTimeout(
|
||||
conf.Network,
|
||||
conf.Addr,
|
||||
conf.ConnTimeout.Duration, // Connect Timeout
|
||||
conf.ConnTimeout.Duration, // Read Timeout
|
||||
conf.ConnTimeout.Duration, // Write Timeout
|
||||
)
|
||||
} else {
|
||||
conn, err = redis.Dial(conf.Network, conf.Addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testOnBorrow(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
conf *config.Cache
|
||||
pool redis.Pool
|
||||
}
|
||||
|
||||
func (p *Pool) Close() error {
|
||||
return p.pool.Close()
|
||||
}
|
||||
|
||||
func (p *Pool) Get() (cache.Tx, error) {
|
||||
return &Tx{
|
||||
conf: p.conf,
|
||||
done: false,
|
||||
multi: false,
|
||||
Conn: p.pool.Get(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tx represents a transaction for Redis with one gotcha:
|
||||
// all reads must be done prior to any writes. Writes will
|
||||
// check if the MULTI command has been sent to redis and will
|
||||
// send it if it hasn't.
|
||||
//
|
||||
// Internally a transaction looks like:
|
||||
// WATCH keyA
|
||||
// GET keyA
|
||||
// WATCH keyB
|
||||
// GET keyB
|
||||
// MULTI
|
||||
// SET keyA
|
||||
// SET keyB
|
||||
// EXEC
|
||||
type Tx struct {
|
||||
conf *config.Cache
|
||||
done bool
|
||||
multi bool
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (tx *Tx) close() {
|
||||
if tx.done {
|
||||
panic("redis: transaction closed twice")
|
||||
}
|
||||
tx.done = true
|
||||
tx.Conn.Close()
|
||||
}
|
||||
|
||||
func (tx *Tx) initiateWrite() error {
|
||||
if tx.done {
|
||||
return cache.ErrTxDone
|
||||
}
|
||||
if tx.multi != true {
|
||||
return tx.Send("MULTI")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) initiateRead() error {
|
||||
if tx.done {
|
||||
return cache.ErrTxDone
|
||||
}
|
||||
if tx.multi == true {
|
||||
panic("Tried to read during MULTI")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Commit() error {
|
||||
if tx.done {
|
||||
return cache.ErrTxDone
|
||||
}
|
||||
if tx.multi == true {
|
||||
_, err := tx.Do("EXEC")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Rollback() error {
|
||||
if tx.done {
|
||||
return cache.ErrTxDone
|
||||
}
|
||||
// Redis doesn't need to do anything. Exec is atomic.
|
||||
tx.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) {
|
||||
err := tx.initiateRead()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
key := tx.conf.Prefix + "user:" + passkey
|
||||
_, err = tx.Do("WATCH", key)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
reply, err := redis.String(tx.Do("GET", key))
|
||||
if err != nil {
|
||||
if err == redis.ErrNil {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
err = json.NewDecoder(strings.NewReader(reply)).Decode(user)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return user, true, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) FindTorrent(infohash string) (*models.Torrent, bool, error) {
|
||||
err := tx.initiateRead()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
key := tx.conf.Prefix + "torrent:" + infohash
|
||||
_, err = tx.Do("WATCH", key)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
reply, err := redis.String(tx.Do("GET", key))
|
||||
if err != nil {
|
||||
if err == redis.ErrNil {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
torrent := &models.Torrent{}
|
||||
err = json.NewDecoder(strings.NewReader(reply)).Decode(torrent)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return torrent, true, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) ClientWhitelisted(peerID string) (exists bool, err error) {
|
||||
err = tx.initiateRead()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
key := tx.conf.Prefix + "whitelist"
|
||||
_, err = tx.Do("WATCH", key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) MarkActive(t *models.Torrent) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) AddLeecher(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) SetLeecher(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) RemoveLeecher(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) AddSeeder(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) SetSeeder(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) RemoveSeeder(t *models.Torrent, p *models.Peer) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) IncrementSlots(u *models.User) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) DecrementSlots(u *models.User) error {
|
||||
if err := tx.initiateWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cache.Register("redis", &driver{})
|
||||
}
|
||||
Reference in New Issue
Block a user