]> git.lizzy.rs Git - go-anidb.git/blob - udp.go
372aeadf20d8afc2117608f236f9614351a7a4b4
[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 (udp *udpWrap) sendQueue() {
103         initialWait := 6 * time.Second
104         wait := initialWait
105         for set := range udp.sendQueueCh {
106         Retry:
107                 if Banned() {
108                         set.ch <- bannedReply
109                         close(set.ch)
110                         continue
111                 }
112
113                 reply := <-udp.AniDBUDP.SendRecv(set.cmd, udpapi.ParamMap(set.params))
114
115                 if reply.Error() == udpapi.TimeoutError {
116                         // retry
117                         wait = (wait * 15) / 10
118                         if wait > time.Minute {
119                                 wait = time.Minute
120                         }
121
122                         time.Sleep(wait)
123                         goto Retry
124                 }
125                 wait = initialWait
126
127                 switch reply.Code() {
128                 case 403, 501, 506: // not logged in, or session expired
129                         if r := udp.ReAuth(); r.Error() == nil {
130                                 // retry
131                                 goto Retry
132                         }
133                 case 503, 504: // client library rejected
134                         panic(reply.Error())
135                 case 555: // IP (and user, possibly client) temporarily banned
136                         setBanned()
137                 }
138                 set.ch <- reply
139                 close(set.ch)
140         }
141 }
142
143 type errorReply struct {
144         udpapi.APIReply
145         err error
146 }
147
148 func (r *errorReply) Code() int {
149         return 999
150 }
151 func (r *errorReply) Text() string {
152         return r.err.Error()
153 }
154 func (r *errorReply) Error() error {
155         return r.err
156 }
157
158 func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply {
159         ch := make(chan udpapi.APIReply, 1)
160
161         udp.sendLock.Lock()
162         defer udp.sendLock.Unlock()
163
164         if Banned() {
165                 ch <- bannedReply
166                 close(ch)
167                 return ch
168         }
169
170         if !udp.connected {
171                 if r := udp.ReAuth(); r.Error() != nil {
172                         ch <- r
173                         close(ch)
174                         return ch
175                 }
176         }
177
178         udp.sendQueueCh <- paramSet{
179                 cmd:    cmd,
180                 params: params,
181                 ch:     ch,
182         }
183
184         return ch
185 }