]> git.lizzy.rs Git - go-anidb.git/blob - udp.go
anidb: Try to get (*AniDB).User() from the cache if it's unset
[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         user *User
50 }
51
52 func newUDPWrap() *udpWrap {
53         u := &udpWrap{
54                 AniDBUDP:    udpapi.NewAniDBUDP(),
55                 sendQueueCh: make(chan paramSet, 10),
56         }
57         go u.sendQueue()
58         return u
59 }
60
61 type paramMap udpapi.ParamMap // shortcut
62
63 type noauthAPIReply struct {
64         udpapi.APIReply
65 }
66
67 func (r *noauthAPIReply) Code() int {
68         return 501
69 }
70
71 func (r *noauthAPIReply) Text() string {
72         return "LOGIN FIRST"
73 }
74
75 func (r *noauthAPIReply) Error() error {
76         return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
77 }
78
79 type bannedAPIReply struct {
80         udpapi.APIReply
81 }
82
83 func (r *bannedAPIReply) Code() int {
84         return 555
85 }
86 func (r *bannedAPIReply) Text() string {
87         return "BANNED"
88 }
89 func (r *bannedAPIReply) Error() error {
90         return &udpapi.APIError{Code: r.Code(), Desc: r.Text()}
91 }
92
93 var bannedReply udpapi.APIReply = &bannedAPIReply{}
94
95 func logRequest(set paramSet) {
96         switch set.cmd {
97         case "AUTH":
98                 log.Printf("UDP>>> AUTH user=%s\n", set.params["user"])
99         default:
100                 log.Printf("UDP>>> %s %s\n", set.cmd, udpapi.ParamMap(set.params).String())
101         }
102 }
103
104 func logReply(reply udpapi.APIReply) {
105         log.Printf("UDP<<< %d %s\n", reply.Code(), reply.Text())
106 }
107
108 func (udp *udpWrap) sendQueue() {
109         initialWait := 5 * time.Second
110         wait := initialWait
111         for set := range udp.sendQueueCh {
112         Retry:
113                 if Banned() {
114                         set.ch <- bannedReply
115                         close(set.ch)
116                         continue
117                 }
118
119                 logRequest(set)
120                 reply := <-udp.AniDBUDP.SendRecv(set.cmd, udpapi.ParamMap(set.params))
121
122                 if reply.Error() == udpapi.TimeoutError {
123                         // retry
124                         wait = wait * 2
125                         if wait > time.Minute {
126                                 wait = time.Minute
127                         }
128                         log.Printf("UDP--- Timeout; waiting %s before retry", wait)
129
130                         delete(set.params, "s")
131                         delete(set.params, "tag")
132
133                         time.Sleep(wait)
134                         goto Retry
135                 }
136                 logReply(reply)
137
138                 wait = initialWait
139
140                 switch reply.Code() {
141                 case 403, 501, 506: // not logged in, or session expired
142                         if r := udp.ReAuth(); r.Error() == nil {
143                                 // retry
144
145                                 delete(set.params, "s")
146                                 delete(set.params, "tag")
147
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 func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply {
163         ch := make(chan udpapi.APIReply, 1)
164
165         udp.sendLock.Lock()
166         defer udp.sendLock.Unlock()
167
168         if Banned() {
169                 ch <- bannedReply
170                 close(ch)
171                 return ch
172         }
173
174         if !udp.connected {
175                 if r := udp.ReAuth(); r.Error() != nil {
176                         ch <- r
177                         close(ch)
178                         return ch
179                 }
180         }
181
182         udp.sendQueueCh <- paramSet{
183                 cmd:    cmd,
184                 params: params,
185                 ch:     ch,
186         }
187
188         return ch
189 }