]> git.lizzy.rs Git - go-anidb.git/blobdiff - udp.go
anidb: Log UDP traffic
[go-anidb.git] / udp.go
diff --git a/udp.go b/udp.go
index 433c9da36219e45f802c084f2a424ab30b9f9542..ec8f83d9d16c9c02018dbdca678521c3317bf947 100644 (file)
--- a/udp.go
+++ b/udp.go
@@ -1,26 +1,42 @@
 package anidb
 
 import (
+       "encoding/gob"
        "github.com/Kovensky/go-anidb/udp"
+       "log"
        "sync"
        "time"
 )
 
-var banTime time.Time
-var banTimeLock sync.Mutex
+func init() {
+       gob.RegisterName("*github.com/Kovensky/go-anidb.banCache", &banCache{})
+}
 
 const banDuration = 30*time.Minute + 1*time.Second
 
+type banCache struct{ time.Time }
+
+func (c *banCache) Touch() {
+       c.Time = time.Now()
+}
+func (c *banCache) IsStale() bool {
+       return time.Now().Sub(c.Time) > banDuration
+}
+
 // Returns whether the last UDP API access returned a 555 BANNED message.
 func Banned() bool {
-       banTimeLock.Lock()
-       banTimeLock.Unlock()
+       var banTime banCache
+       cache.Get(&banTime, "banned")
 
-       return _banned()
+       stale := banTime.IsStale()
+       if stale {
+               cache.Delete("banned")
+       }
+       return !stale
 }
 
-func _banned() bool {
-       return time.Now().Sub(banTime) > banDuration
+func setBanned() {
+       cache.Set(&banCache{}, "banned")
 }
 
 type paramSet struct {
@@ -32,8 +48,10 @@ type paramSet struct {
 type udpWrap struct {
        *udpapi.AniDBUDP
 
+       sendLock    sync.Mutex
        sendQueueCh chan paramSet
 
+       credLock    sync.Mutex
        credentials *credentials
        connected   bool
 }
@@ -49,6 +67,22 @@ func newUDPWrap() *udpWrap {
 
 type paramMap udpapi.ParamMap // shortcut
 
+type noauthAPIReply struct {
+       udpapi.APIReply
+}
+
+func (r *noauthAPIReply) Code() int {
+       return 501
+}
+
+func (r *noauthAPIReply) Text() string {
+       return "LOGIN FIRST"
+}
+
+func (r *noauthAPIReply) Error() error {
+       return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
+}
+
 type bannedAPIReply struct {
        udpapi.APIReply
 }
@@ -60,55 +94,104 @@ func (r *bannedAPIReply) Text() string {
        return "BANNED"
 }
 func (r *bannedAPIReply) Error() error {
-       return &udpapi.APIError{Code: 555, Desc: "BANNED"}
+       return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
 }
 
 var bannedReply udpapi.APIReply = &bannedAPIReply{}
 
+func logRequest(set paramSet) {
+       switch set.cmd {
+       case "AUTH":
+               log.Printf("UDP>>> AUTH user=%s\n", set.params["user"])
+       default:
+               log.Printf("UDP>>> %s %s\n", set.cmd, udpapi.ParamMap(set.params).String())
+       }
+}
+
+func logReply(reply udpapi.APIReply) {
+       log.Printf("UDP<<< %d %s\n", reply.Code(), reply.Text())
+}
+
 func (udp *udpWrap) sendQueue() {
+       initialWait := 6 * time.Second
+       wait := initialWait
        for set := range udp.sendQueueCh {
+       Retry:
+               if Banned() {
+                       set.ch <- bannedReply
+                       close(set.ch)
+                       continue
+               }
+
+               logRequest(set)
                reply := <-udp.AniDBUDP.SendRecv(set.cmd, udpapi.ParamMap(set.params))
 
                if reply.Error() == udpapi.TimeoutError {
                        // retry
-                       go func(set paramSet) { udp.sendQueueCh <- set }(set)
-                       continue
+                       wait = (wait * 15) / 10
+                       if wait > time.Minute {
+                               wait = time.Minute
+                       }
+                       log.Printf("UDP--- Timeout; waiting %s before retry", wait)
+
+                       time.Sleep(wait)
+                       goto Retry
                }
+               logReply(reply)
+
+               wait = initialWait
 
                switch reply.Code() {
                case 403, 501, 506: // not logged in, or session expired
-                       if err := udp.ReAuth(); err == nil {
+                       if r := udp.ReAuth(); r.Error() == nil {
                                // retry
-                               go func(set paramSet) { udp.sendQueueCh <- set }(set)
-                               continue
+                               goto Retry
                        }
                case 503, 504: // client library rejected
                        panic(reply.Error())
                case 555: // IP (and user, possibly client) temporarily banned
-                       banTimeLock.Lock()
-
-                       banTime = time.Now()
-
-                       banTimeLock.Unlock()
+                       setBanned()
                }
                set.ch <- reply
                close(set.ch)
        }
 }
 
+type errorReply struct {
+       udpapi.APIReply
+       err error
+}
+
+func (r *errorReply) Code() int {
+       return 999
+}
+func (r *errorReply) Text() string {
+       return r.err.Error()
+}
+func (r *errorReply) Error() error {
+       return r.err
+}
+
 func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply {
        ch := make(chan udpapi.APIReply, 1)
 
-       banTimeLock.Lock()
-       defer banTimeLock.Unlock()
-       if _banned() {
-               banTime = time.Time{}
-       } else {
+       udp.sendLock.Lock()
+       defer udp.sendLock.Unlock()
+
+       if Banned() {
                ch <- bannedReply
                close(ch)
                return ch
        }
 
+       if !udp.connected {
+               if r := udp.ReAuth(); r.Error() != nil {
+                       ch <- r
+                       close(ch)
+                       return ch
+               }
+       }
+
        udp.sendQueueCh <- paramSet{
                cmd:    cmd,
                params: params,