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