]> git.lizzy.rs Git - go-anidb.git/blob - udp.go
anidb: Handle UDP API 601 (server down)
[go-anidb.git] / udp.go
1 package anidb
2
3 import (
4         "encoding/gob"
5         "github.com/Kovensky/go-anidb/udp"
6         "log"
7         "sync"
8         "time"
9 )
10
11 func init() {
12         gob.RegisterName("*github.com/Kovensky/go-anidb.banCache", &banCache{})
13 }
14
15 const banDuration = 30*time.Minute + 1*time.Second
16
17 type banCache struct{ time.Time }
18
19 func (c *banCache) Touch() {
20         c.Time = time.Now()
21 }
22 func (c *banCache) IsStale() bool {
23         return time.Now().Sub(c.Time) > banDuration
24 }
25
26 // Returns whether the last UDP API access returned a 555 BANNED message.
27 func Banned() bool {
28         var banTime banCache
29         cache.Get(&banTime, "banned")
30
31         stale := banTime.IsStale()
32         if stale {
33                 cache.Delete("banned")
34         }
35         return !stale
36 }
37
38 func setBanned() {
39         cache.Set(&banCache{}, "banned")
40 }
41
42 type paramSet struct {
43         cmd    string
44         params paramMap
45         ch     chan udpapi.APIReply
46 }
47
48 type udpWrap struct {
49         *udpapi.AniDBUDP
50
51         sendLock    sync.Mutex
52         sendQueueCh chan paramSet
53
54         credLock    sync.Mutex
55         credentials *credentials
56         connected   bool
57 }
58
59 func newUDPWrap() *udpWrap {
60         u := &udpWrap{
61                 AniDBUDP:    udpapi.NewAniDBUDP(),
62                 sendQueueCh: make(chan paramSet, 10),
63         }
64         go u.sendQueue()
65         return u
66 }
67
68 type paramMap udpapi.ParamMap // shortcut
69
70 type noauthAPIReply struct {
71         udpapi.APIReply
72 }
73
74 func (r *noauthAPIReply) Code() int {
75         return 501
76 }
77
78 func (r *noauthAPIReply) Text() string {
79         return "LOGIN FIRST"
80 }
81
82 func (r *noauthAPIReply) Error() error {
83         return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
84 }
85
86 type bannedAPIReply struct {
87         udpapi.APIReply
88 }
89
90 func (r *bannedAPIReply) Code() int {
91         return 555
92 }
93 func (r *bannedAPIReply) Text() string {
94         return "BANNED"
95 }
96 func (r *bannedAPIReply) Error() error {
97         return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
98 }
99
100 var bannedReply udpapi.APIReply = &bannedAPIReply{}
101
102 func logRequest(set paramSet) {
103         switch set.cmd {
104         case "AUTH":
105                 log.Printf("UDP>>> AUTH user=%s\n", set.params["user"])
106         default:
107                 log.Printf("UDP>>> %s %s\n", set.cmd, udpapi.ParamMap(set.params).String())
108         }
109 }
110
111 func logReply(reply udpapi.APIReply) {
112         log.Printf("UDP<<< %d %s\n", reply.Code(), reply.Text())
113 }
114
115 func (udp *udpWrap) sendQueue() {
116         initialWait := 6 * time.Second
117         wait := initialWait
118         for set := range udp.sendQueueCh {
119         Retry:
120                 if Banned() {
121                         set.ch <- bannedReply
122                         close(set.ch)
123                         continue
124                 }
125
126                 logRequest(set)
127                 reply := <-udp.AniDBUDP.SendRecv(set.cmd, udpapi.ParamMap(set.params))
128
129                 if reply.Error() == udpapi.TimeoutError {
130                         // retry
131                         wait = (wait * 15) / 10
132                         if wait > time.Minute {
133                                 wait = time.Minute
134                         }
135                         log.Printf("UDP--- Timeout; waiting %s before retry", wait)
136
137                         time.Sleep(wait)
138                         goto Retry
139                 }
140                 logReply(reply)
141
142                 wait = initialWait
143
144                 switch reply.Code() {
145                 case 403, 501, 506: // not logged in, or session expired
146                         if r := udp.ReAuth(); r.Error() == nil {
147                                 // retry
148                                 goto Retry
149                         }
150                 case 503, 504: // client library rejected
151                         panic(reply.Error())
152                 // 555: IP (and user, possibly client) temporarily banned
153                 // 601: Server down (treat the same as a ban)
154                 case 555, 601:
155                         setBanned()
156                 }
157                 set.ch <- reply
158                 close(set.ch)
159         }
160 }
161
162 type errorReply struct {
163         udpapi.APIReply
164         err error
165 }
166
167 func (r *errorReply) Code() int {
168         return 999
169 }
170 func (r *errorReply) Text() string {
171         return r.err.Error()
172 }
173 func (r *errorReply) Error() error {
174         return r.err
175 }
176
177 func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply {
178         ch := make(chan udpapi.APIReply, 1)
179
180         udp.sendLock.Lock()
181         defer udp.sendLock.Unlock()
182
183         if Banned() {
184                 ch <- bannedReply
185                 close(ch)
186                 return ch
187         }
188
189         if !udp.connected {
190                 if r := udp.ReAuth(); r.Error() != nil {
191                         ch <- r
192                         close(ch)
193                         return ch
194                 }
195         }
196
197         udp.sendQueueCh <- paramSet{
198                 cmd:    cmd,
199                 params: params,
200                 ch:     ch,
201         }
202
203         return ch
204 }