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