Use value order independent response parsing

This commit is contained in:
cpb8010 2013-09-28 11:03:21 -04:00
parent 11c0c3cb3c
commit f113bfb846

211
cache/redis/redis.go vendored
View file

@ -112,85 +112,92 @@ func (tx *Tx) close() {
tx.Conn.Close() tx.Conn.Close()
} }
// createUser takes a slice of length 7 and returns a pointer to a new models.User or an error. // createUser takes a string slice of length 14 and returns a pointer to a new
// This function is used to create a user from a Redis hash response. // models.User or an error.
// The 7 strings in the slice must be in the specified order. // This function is used to create a user from a Redis hash response(HGETALL).
// // The order of strings the in the slice must follow the pattern:
// User.ID // [<field name>, <field value>, <field name>, <field value>, ...]
// User.Passkey // If the field value string cannot be converted to the correct type,
// User.UpMultiplier
// User.DownMultiplier
// User.Slots
// User.SlotsUsed
// User.Snatches
//
// If the strings cannot be converted to the correct type,
// createUser will return a nil user and the conversion error. // createUser will return a nil user and the conversion error.
func createUser(userVals []string) (*models.User, error) { func createUser(userVals []string) (*models.User, error) {
if len(userVals) != 7 { if len(userVals) != 14 {
return nil, ErrCreateUser return nil, ErrCreateUser
} }
var user models.User var user models.User
convErrors := make([]error, 7) var err error
user.ID, convErrors[0] = strconv.ParseUint(userVals[0], 10, 64) for index, userString := range userVals {
user.Passkey = userVals[1] switch userString {
user.UpMultiplier, convErrors[2] = strconv.ParseFloat(userVals[2], 64) case "id":
user.DownMultiplier, convErrors[3] = strconv.ParseFloat(userVals[3], 64) user.ID, err = strconv.ParseUint(userVals[index+1], 10, 64)
user.Slots, convErrors[4] = strconv.ParseInt(userVals[4], 10, 64) case "passkey":
user.SlotsUsed, convErrors[5] = strconv.ParseInt(userVals[5], 10, 64) user.Passkey = userVals[index+1]
user.Snatches, convErrors[6] = strconv.ParseUint(userVals[6], 10, 64) case "up_multiplier":
user.UpMultiplier, err = strconv.ParseFloat(userVals[index+1], 64)
for i := 0; i < 7; i++ { case "down_multiplier":
if convErrors[i] != nil { user.DownMultiplier, err = strconv.ParseFloat(userVals[index+1], 64)
return nil, convErrors[i] case "slots":
user.Slots, err = strconv.ParseInt(userVals[index+1], 10, 64)
case "slots_used":
user.SlotsUsed, err = strconv.ParseInt(userVals[index+1], 10, 64)
case "snatches":
user.Snatches, err = strconv.ParseUint(userVals[index+1], 10, 64)
}
if err != nil {
return nil, err
} }
} }
return &user, nil return &user, nil
} }
// createTorrent takes a slice of length 7 and returns a pointer to a new models.Torrent or an error. // createTorrent takes a string slice of length 14 and returns a pointer to a new models.Torrent
// This function can be used to create a torrent from a Redis hash response. // or an error.
// The 7 hash fields must be in the specified order. // This function can be used to create a torrent from a Redis hash response(HGETALL).
// // The order of strings the in the slice must follow the pattern:
// torrent.ID // [<field name>, <field value>, <field name>, <field value>, ...]
// torrent.Infohash
// torrent.Active
// torrent.Snatches
// torrent.UpMultiplier
// torrent.DownMultiplier
// torrent.LastAction
//
// This function calls multiple redis commands, it's not internally atomic. // This function calls multiple redis commands, it's not internally atomic.
// After converting the torrent's ID, the seeders and leechers are populated by getPeers // If the field values cannot be converted to the correct type,
// If the strings cannot be converted to the correct type,
// createTorrent will return a nil user and the conversion error. // createTorrent will return a nil user and the conversion error.
// After converting the torrent fields, the seeders and leechers are populated by redis.getPeers
func (tx *Tx) createTorrent(torrentVals []string) (*models.Torrent, error) { func (tx *Tx) createTorrent(torrentVals []string) (*models.Torrent, error) {
if len(torrentVals) != 7 { if len(torrentVals) != 14 {
return nil, ErrCreateTorrent return nil, ErrCreateTorrent
} }
var torrent models.Torrent var torrent models.Torrent
convErrors := make([]error, 9) var err error
torrent.ID, convErrors[0] = strconv.ParseUint(torrentVals[0], 10, 64) for index, torrentString := range torrentVals {
torrent.Infohash = torrentVals[1] switch torrentString {
torrent.Active, convErrors[2] = strconv.ParseBool(torrentVals[2]) case "id":
torrent.Snatches, convErrors[3] = strconv.ParseUint(torrentVals[3], 10, 32) torrent.ID, err = strconv.ParseUint(torrentVals[index+1], 10, 64)
torrent.UpMultiplier, convErrors[4] = strconv.ParseFloat(torrentVals[4], 64) case "infohash":
torrent.DownMultiplier, convErrors[5] = strconv.ParseFloat(torrentVals[5], 64) torrent.Infohash = torrentVals[index+1]
torrent.LastAction, convErrors[6] = strconv.ParseInt(torrentVals[6], 10, 64) case "active":
torrent.Seeders, convErrors[7] = tx.getPeers(torrent.ID, SeedersPrefix) torrent.Active, err = strconv.ParseBool(torrentVals[index+1])
torrent.Leechers, convErrors[8] = tx.getPeers(torrent.ID, LeechersPrefix) case "snatches":
torrent.Snatches, err = strconv.ParseUint(torrentVals[index+1], 10, 32)
for i := 0; i < 9; i++ { case "up_multiplier":
if convErrors[i] != nil { torrent.UpMultiplier, err = strconv.ParseFloat(torrentVals[index+1], 64)
return nil, convErrors[i] case "down_multiplier":
torrent.DownMultiplier, err = strconv.ParseFloat(torrentVals[index+1], 64)
case "last_action":
torrent.LastAction, err = strconv.ParseInt(torrentVals[index+1], 10, 64)
} }
if err != nil {
return nil, err
}
}
torrent.Seeders, err = tx.getPeers(torrent.ID, SeedersPrefix)
if err != nil {
return nil, err
}
torrent.Leechers, err = tx.getPeers(torrent.ID, LeechersPrefix)
if err != nil {
return nil, err
} }
return &torrent, nil return &torrent, nil
} }
// setPeer writes or overwrites peer information. // setPeer writes or overwrites peer information, stored as a Redis hash.
// The hash fields are sent in a specific order // The hash fields names are the same as the JSON tags on the models.Peer struct.
// so that they can be unpacked correctly
func (tx *Tx) setPeer(peer *models.Peer) error { func (tx *Tx) setPeer(peer *models.Peer) error {
hashKey := tx.conf.Prefix + getPeerHashKey(peer) hashKey := tx.conf.Prefix + getPeerHashKey(peer)
_, err := tx.Do("HMSET", hashKey, _, err := tx.Do("HMSET", hashKey,
@ -274,47 +281,47 @@ func (tx *Tx) addPeers(peers map[string]models.Peer, peerTypePrefix string) erro
} }
// createPeer takes a slice of length 9 and returns a pointer to a new models.Peer or an error. // createPeer takes a slice of length 9 and returns a pointer to a new models.Peer or an error.
// This function is used to create a peer from a Redis hash response. // This function is used to create a peer from a Redis hash response(HGETALL).
// The 9 strings in the slice must be in the specified order. // The order of strings the in the slice must follow the pattern:
// // [<field name>, <field value>, <field name>, <field value>, ...]
// peer.ID // If the field value string cannot be converted to the correct type,
// peer.UserID // the function will return a nil peer and the conversion error.
// peer.TorrentID
// peer.IP
// peer.Port
// peer.Uploaded
// peer.Downloaded
// peer.Left
// peer.LastAnnounce
//
// If the strings cannot be converted to the correct type,
// This function will return a nil peer and the conversion error.
func createPeer(peerVals []string) (*models.Peer, error) { func createPeer(peerVals []string) (*models.Peer, error) {
if len(peerVals) != 9 { if len(peerVals) != 18 {
return nil, ErrCreatePeer return nil, ErrCreatePeer
} }
var peer models.Peer var peer models.Peer
convErrors := make([]error, 9) var err error
peer.ID = peerVals[0] for index, peerString := range peerVals {
peer.UserID, convErrors[1] = strconv.ParseUint(peerVals[1], 10, 64) switch peerString {
peer.TorrentID, convErrors[2] = strconv.ParseUint(peerVals[2], 10, 64) case "id":
peer.IP = peerVals[3] peer.ID = peerVals[index+1]
peer.Port, convErrors[4] = strconv.ParseUint(peerVals[4], 10, 64) case "user_id":
peer.Uploaded, convErrors[5] = strconv.ParseUint(peerVals[5], 10, 64) peer.UserID, err = strconv.ParseUint(peerVals[index+1], 10, 64)
peer.Downloaded, convErrors[6] = strconv.ParseUint(peerVals[6], 10, 64) case "torrent_id":
peer.Left, convErrors[7] = strconv.ParseUint(peerVals[7], 10, 64) peer.TorrentID, err = strconv.ParseUint(peerVals[index+1], 10, 64)
peer.LastAnnounce, convErrors[8] = strconv.ParseInt(peerVals[8], 10, 64) case "ip":
peer.IP = peerVals[index+1]
for i := 0; i < 9; i++ { case "port":
if convErrors[i] != nil { peer.Port, err = strconv.ParseUint(peerVals[index+1], 10, 64)
return nil, convErrors[i] case "uploaded":
peer.Uploaded, err = strconv.ParseUint(peerVals[index+1], 10, 64)
case "downloaded":
peer.Downloaded, err = strconv.ParseUint(peerVals[index+1], 10, 64)
case "left":
peer.Left, err = strconv.ParseUint(peerVals[index+1], 10, 64)
case "last_announce":
peer.LastAnnounce, err = strconv.ParseInt(peerVals[index+1], 10, 64)
}
if err != nil {
return nil, err
} }
} }
return &peer, nil return &peer, nil
} }
// getPeers returns a map of peers from a specified torrent's peer set(seeders or leechers). // getPeers returns a map of peers from a specified torrent's peer set(seeders or leechers).
// This is a multiple action command, it's not internally atomic // This is a multiple action command, it's not internally atomic.
func (tx *Tx) getPeers(torrentID uint64, peerTypePrefix string) (peers map[string]models.Peer, err error) { func (tx *Tx) getPeers(torrentID uint64, peerTypePrefix string) (peers map[string]models.Peer, err error) {
peers = make(map[string]models.Peer) peers = make(map[string]models.Peer)
setKey := tx.conf.Prefix + peerTypePrefix + strconv.FormatUint(torrentID, 36) setKey := tx.conf.Prefix + peerTypePrefix + strconv.FormatUint(torrentID, 36)
@ -325,7 +332,7 @@ func (tx *Tx) getPeers(torrentID uint64, peerTypePrefix string) (peers map[strin
// Keys map to peer objects stored in hashes // Keys map to peer objects stored in hashes
for _, peerHashKey := range peerStrings { for _, peerHashKey := range peerStrings {
hashKey := tx.conf.Prefix + peerHashKey hashKey := tx.conf.Prefix + peerHashKey
peerVals, err := redis.Strings(tx.Do("HVALS", hashKey)) peerVals, err := redis.Strings(tx.Do("HGETALL", hashKey))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -342,9 +349,8 @@ func (tx *Tx) getPeers(torrentID uint64, peerTypePrefix string) (peers map[strin
} }
// AddTorrent writes/overwrites torrent information and saves peers from both peer sets. // AddTorrent writes/overwrites torrent information and saves peers from both peer sets.
// The hash fields are sent in a specific order // The hash fields names are the same as the JSON tags on the models.Torrent struct.
// so that they can be unpacked correctly // This is a multiple action command, it's not internally atomic.
// This is a multiple action command, it's not internally atomic
func (tx *Tx) AddTorrent(t *models.Torrent) error { func (tx *Tx) AddTorrent(t *models.Torrent) error {
hashkey := tx.conf.Prefix + TorrentPrefix + t.Infohash hashkey := tx.conf.Prefix + TorrentPrefix + t.Infohash
_, err := tx.Do("HMSET", hashkey, _, err := tx.Do("HMSET", hashkey,
@ -392,8 +398,7 @@ func (tx *Tx) RemoveTorrent(t *models.Torrent) error {
} }
// AddUser writes/overwrites user information to a Redis hash. // AddUser writes/overwrites user information to a Redis hash.
// The hash fields are sent in a specific order // The hash fields names are the same as the JSON tags on the models.user struct.
// so that they can be unpacked correctly
func (tx *Tx) AddUser(u *models.User) error { func (tx *Tx) AddUser(u *models.User) error {
hashkey := tx.conf.Prefix + UserPrefix + u.Passkey hashkey := tx.conf.Prefix + UserPrefix + u.Passkey
_, err := tx.Do("HMSET", hashkey, _, err := tx.Do("HMSET", hashkey,
@ -421,13 +426,13 @@ func (tx *Tx) RemoveUser(u *models.User) error {
return nil return nil
} }
// FindUser returns true and a pointer to a new user struct, if the user exists // FindUser returns a pointer to a new user struct and true if the user exists,
// or nil and false if the user doesn't exist. // or nil and false if the user doesn't exist.
// This function does not return an error if the torrent doesn't exist. // This function does not return an error if the torrent doesn't exist.
func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) { func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) {
hashkey := tx.conf.Prefix + UserPrefix + passkey hashkey := tx.conf.Prefix + UserPrefix + passkey
// Consider using HGETALL instead of HVALS here for robustness // Consider using HGETALL instead of HVALS here for robustness
userStrings, err := redis.Strings(tx.Do("HVALS", hashkey)) userStrings, err := redis.Strings(tx.Do("HGETALL", hashkey))
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} else if len(userStrings) == 0 { } else if len(userStrings) == 0 {
@ -440,12 +445,12 @@ func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) {
return foundUser, true, nil return foundUser, true, nil
} }
// FindTorrent returns a pointer to a new torrent struct and true, if the torrent exists // FindTorrent returns a pointer to a new torrent struct and true if the torrent exists,
// or nil and false if the torrent doesn't exist. // or nil and false if the torrent doesn't exist.
// This is a multiple action command, it's not internally atomic // This is a multiple action command, it's not internally atomic.
func (tx *Tx) FindTorrent(infohash string) (*models.Torrent, bool, error) { func (tx *Tx) FindTorrent(infohash string) (*models.Torrent, bool, error) {
hashkey := tx.conf.Prefix + TorrentPrefix + infohash hashkey := tx.conf.Prefix + TorrentPrefix + infohash
torrentStrings, err := redis.Strings(tx.Do("HVALS", hashkey)) torrentStrings, err := redis.Strings(tx.Do("HGETALL", hashkey))
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} else if len(torrentStrings) == 0 { } else if len(torrentStrings) == 0 {
@ -485,7 +490,7 @@ func (tx *Tx) UnWhitelistClient(peerID string) error {
// RecordSnatch increments the snatch counter on the torrent and user by one. // RecordSnatch increments the snatch counter on the torrent and user by one.
// This modifies the arguments as well as the hash field in Redis. // This modifies the arguments as well as the hash field in Redis.
// This is a multiple action command, it's not internally atomic // This is a multiple action command, it's not internally atomic.
func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error { func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error {
torrentKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash torrentKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash
@ -546,7 +551,7 @@ func (tx *Tx) MarkInactive(torrent *models.Torrent) error {
// AddLeecher adds a new peer to a torrent's leecher set. // AddLeecher adds a new peer to a torrent's leecher set.
// This modifies the torrent argument, as well as the torrent's set and peer's hash in Redis. // This modifies the torrent argument, as well as the torrent's set and peer's hash in Redis.
// This function does not return an error if the leecher already exists. // This function does not return an error if the leecher already exists.
// This is a multiple action command, it's not internally atomic // This is a multiple action command, it's not internally atomic.
func (tx *Tx) AddLeecher(torrent *models.Torrent, peer *models.Peer) error { func (tx *Tx) AddLeecher(torrent *models.Torrent, peer *models.Peer) error {
setKey := tx.conf.Prefix + LeechersPrefix + strconv.FormatUint(torrent.ID, 36) setKey := tx.conf.Prefix + LeechersPrefix + strconv.FormatUint(torrent.ID, 36)
_, err := tx.Do("SADD", setKey, getPeerHashKey(peer)) _, err := tx.Do("SADD", setKey, getPeerHashKey(peer))
@ -611,7 +616,7 @@ func (tx *Tx) LeecherFinished(torrent *models.Torrent, peer *models.Peer) error
// AddSeeder adds a new peer to a torrent's seeder set. // AddSeeder adds a new peer to a torrent's seeder set.
// This modifies the torrent argument, as well as the torrent's set and peer's hash in Redis. // This modifies the torrent argument, as well as the torrent's set and peer's hash in Redis.
// This function does not return an error if the seeder already exists. // This function does not return an error if the seeder already exists.
// This is a multiple action command, it's not internally atomic // This is a multiple action command, it's not internally atomic.
func (tx *Tx) AddSeeder(torrent *models.Torrent, peer *models.Peer) error { func (tx *Tx) AddSeeder(torrent *models.Torrent, peer *models.Peer) error {
setKey := tx.conf.Prefix + SeedersPrefix + strconv.FormatUint(torrent.ID, 36) setKey := tx.conf.Prefix + SeedersPrefix + strconv.FormatUint(torrent.ID, 36)
_, err := tx.Do("SADD", setKey, getPeerHashKey(peer)) _, err := tx.Do("SADD", setKey, getPeerHashKey(peer))