]> git.lizzy.rs Git - go-anidb.git/blob - usercache.go
anidb: Automatically run USER query after AUTH
[go-anidb.git] / usercache.go
1 package anidb
2
3 import (
4         "github.com/Kovensky/go-anidb/udp"
5         "github.com/Kovensky/go-fscache"
6         "strconv"
7         "strings"
8         "sync"
9         "time"
10 )
11
12 type UID int
13
14 func (uid UID) User() *User {
15         var u User
16         if CacheGet(&u, "user", uid) == nil {
17                 return &u
18         }
19         return nil
20 }
21
22 func (adb *AniDB) GetCurrentUser() <-chan *User {
23         ch := make(chan *User, 1)
24
25         if adb.udp.credentials == nil {
26                 ch <- nil
27                 close(ch)
28                 return ch
29         }
30
31         return adb.GetUserByName(decrypt(adb.udp.credentials.username))
32 }
33
34 // This is an (almost) entirely local representation.
35 func (adb *AniDB) GetUserByID(uid UID) <-chan *User {
36         key := []fscache.CacheKey{"user", uid}
37         ch := make(chan *User, 1)
38
39         if uid < 1 {
40                 ch <- nil
41                 close(ch)
42                 return ch
43         }
44
45         ic := make(chan notification, 1)
46         go func() { ch <- (<-ic).(*User); close(ch) }()
47         if intentMap.Intent(ic, key...) {
48                 return ch
49         }
50
51         if !Cache.IsValid(InvalidKeyCacheDuration, key...) {
52                 intentMap.NotifyClose((*User)(nil), key...)
53                 return ch
54         }
55
56         go func() {
57                 var user *User
58                 if CacheGet(&user, key...) == nil {
59                         intentMap.NotifyClose(user, key...)
60                         return
61                 }
62                 <-adb.GetUserName(uid)
63
64                 CacheGet(&user, key...)
65                 intentMap.NotifyClose(user)
66         }()
67         return ch
68 }
69
70 func (adb *AniDB) GetUserByName(username string) <-chan *User {
71         ch := make(chan *User, 1)
72
73         if username == "" {
74                 ch <- nil
75                 close(ch)
76                 return ch
77         }
78
79         go func() {
80                 ch <- <-adb.GetUserByID(<-adb.GetUserUID(username))
81                 close(ch)
82         }()
83         return ch
84 }
85
86 func (adb *AniDB) GetUserUID(username string) <-chan UID {
87         key := []fscache.CacheKey{"user", "by-name", username}
88         ch := make(chan UID, 1)
89
90         if username == "" {
91                 ch <- 0
92                 close(ch)
93                 return ch
94         }
95
96         ic := make(chan notification, 1)
97         go func() { ch <- (<-ic).(UID); close(ch) }()
98         if intentMap.Intent(ic, key...) {
99                 return ch
100         }
101
102         if !Cache.IsValid(InvalidKeyCacheDuration, key...) {
103                 intentMap.NotifyClose((UID)(0), key...)
104                 return ch
105         }
106
107         uid := UID(0)
108         switch ts, err := Cache.Get(&uid, key...); {
109         case err == nil && time.Now().Sub(ts) < UIDCacheDuration:
110                 intentMap.NotifyClose(uid, key...)
111                 return ch
112         }
113
114         go func() {
115                 reply := <-adb.udp.SendRecv("USER",
116                         paramMap{"user": username})
117
118                 switch reply.Code() {
119                 case 295:
120                         uid, _ = parseUserReply(reply) // caches
121                 case 394:
122                         Cache.SetInvalid(key...)
123                 }
124
125                 intentMap.NotifyClose(uid, key...)
126         }()
127         return ch
128 }
129
130 func (adb *AniDB) GetUserName(uid UID) <-chan string {
131         key := []fscache.CacheKey{"user", "by-uid", uid}
132         ch := make(chan string, 1)
133
134         if uid < 1 {
135                 ch <- ""
136                 close(ch)
137                 return ch
138         }
139
140         ic := make(chan notification, 1)
141         go func() { ch <- (<-ic).(string); close(ch) }()
142         if intentMap.Intent(ic, key...) {
143                 return ch
144         }
145
146         if !Cache.IsValid(InvalidKeyCacheDuration, key...) {
147                 intentMap.NotifyClose("", key...)
148                 return ch
149         }
150
151         name := ""
152         switch ts, err := Cache.Get(&name, key...); {
153         case err == nil && time.Now().Sub(ts) < UIDCacheDuration:
154                 intentMap.NotifyClose(name, key...)
155                 return ch
156         }
157
158         go func() {
159                 reply := <-adb.udp.SendRecv("USER",
160                         paramMap{"uid": uid})
161
162                 switch reply.Code() {
163                 case 295:
164                         _, name = parseUserReply(reply) // caches
165                 case 394:
166                         Cache.SetInvalid(key...)
167                 }
168
169                 intentMap.NotifyClose(name, key...)
170         }()
171         return ch
172 }
173
174 var userReplyMutex sync.Mutex
175
176 func parseUserReply(reply udpapi.APIReply) (UID, string) {
177         userReplyMutex.Lock()
178         defer userReplyMutex.Unlock()
179
180         if reply.Error() == nil {
181                 parts := strings.Split(reply.Lines()[1], "|")
182                 id, _ := strconv.ParseInt(parts[0], 10, 32)
183
184                 CacheSet(UID(id), "user", "by-name", parts[1])
185                 CacheSet(parts[1], "user", "by-uid", id)
186
187                 if _, err := Cache.Stat("user", id); err != nil {
188                         CacheSet(&User{
189                                 UID:      UID(id),
190                                 Username: parts[1],
191                         }, "user", id)
192                 }
193
194                 return UID(id), parts[1]
195         }
196         return 0, ""
197 }