]> git.lizzy.rs Git - go-anidb.git/blob - groupcache.go
anidb: Wait longer after each UDP API timeout
[go-anidb.git] / groupcache.go
1 package anidb
2
3 import (
4         "encoding/gob"
5         "github.com/Kovensky/go-anidb/http"
6         "github.com/Kovensky/go-anidb/udp"
7         "strconv"
8         "strings"
9         "time"
10 )
11
12 func init() {
13         gob.RegisterName("*github.com/Kovensky/go-anidb.Group", &Group{})
14         gob.RegisterName("github.com/Kovensky/go-anidb.GID", GID(0))
15         gob.RegisterName("*github.com/Kovensky/go-anidb.gidCache", &gidCache{})
16 }
17
18 func (g *Group) Touch() {
19         g.Cached = time.Now()
20 }
21
22 func (g *Group) IsStale() bool {
23         if g == nil {
24                 return true
25         }
26         return time.Now().Sub(g.Cached) > GroupCacheDuration
27 }
28
29 // Unique Group IDentifier
30 type GID int
31
32 // make GID cacheable
33
34 func (e GID) Touch()        {}
35 func (e GID) IsStale() bool { return false }
36
37 // Retrieves the Group from the cache.
38 func (gid GID) Group() *Group {
39         var g Group
40         if cache.Get(&g, "gid", gid) == nil {
41                 return &g
42         }
43         return nil
44 }
45
46 type gidCache struct {
47         GID
48         Time time.Time
49 }
50
51 func (c *gidCache) Touch() { c.Time = time.Now() }
52 func (c *gidCache) IsStale() bool {
53         if c != nil && time.Now().Sub(c.Time) < GroupCacheDuration {
54                 return false
55         }
56         return true
57 }
58
59 // Returns a Group from the cache if possible.
60 //
61 // If the Group is stale, then retrieves the Group
62 // through the UDP API.
63 func (adb *AniDB) GroupByID(gid GID) <-chan *Group {
64         keys := []cacheKey{"gid", gid}
65         ch := make(chan *Group, 1)
66
67         ic := make(chan Cacheable, 1)
68         go func() { ch <- (<-ic).(*Group); close(ch) }()
69         if intentMap.Intent(ic, keys...) {
70                 return ch
71         }
72
73         if !cache.CheckValid(keys...) {
74                 intentMap.Notify((*Group)(nil), keys...)
75                 return ch
76         }
77
78         if g := gid.Group(); !g.IsStale() {
79                 intentMap.Notify(g, keys...)
80                 return ch
81         }
82
83         go func() {
84                 reply := <-adb.udp.SendRecv("GROUP",
85                         paramMap{"gid": gid})
86
87                 var g *Group
88                 if reply.Error() == nil {
89                         g = parseGroupReply(reply)
90                 } else if reply.Code() == 350 {
91                         cache.MarkInvalid(keys...)
92                 }
93                 if g != nil {
94                         cache.Set(&gidCache{GID: g.GID}, "gid", "by-name", g.Name)
95                         cache.Set(&gidCache{GID: g.GID}, "gid", "by-shortname", g.ShortName)
96                         cache.Set(g, keys...)
97                 }
98
99                 intentMap.Notify(g, keys...)
100         }()
101         return ch
102 }
103
104 func (adb *AniDB) GroupByName(gname string) <-chan *Group {
105         keys := []cacheKey{"gid", "by-name", gname}
106         altKeys := []cacheKey{"gid", "by-shortname", gname}
107         ch := make(chan *Group, 1)
108
109         ic := make(chan Cacheable, 1)
110         go func() {
111                 gid := (<-ic).(GID)
112                 if gid > 0 {
113                         ch <- <-adb.GroupByID(gid)
114                 }
115                 close(ch)
116         }()
117         if intentMap.Intent(ic, keys...) {
118                 return ch
119         }
120
121         if !cache.CheckValid(keys...) {
122                 intentMap.Notify(GID(0), keys...)
123                 return ch
124         }
125
126         var gc gidCache
127         if cache.Get(&gc, keys...) == nil {
128                 intentMap.Notify(gc.GID, keys...)
129                 return ch
130         }
131
132         if cache.Get(&gc, altKeys...) == nil {
133                 intentMap.Notify(gc.GID, keys...)
134                 return ch
135         }
136
137         go func() {
138                 reply := <-adb.udp.SendRecv("GROUP",
139                         paramMap{"gname": gname})
140
141                 var g *Group
142                 if reply.Error() == nil {
143                         g = parseGroupReply(reply)
144                 } else if reply.Code() == 350 {
145                         cache.MarkInvalid(keys...)
146                 }
147
148                 gid := GID(0)
149                 if g != nil {
150                         gid = g.GID
151
152                         cache.Set(&gidCache{GID: gid}, keys...)
153                         cache.Set(&gidCache{GID: gid}, altKeys...)
154                         cache.Set(g, "gid", gid)
155                 }
156                 intentMap.Notify(gid, keys...)
157         }()
158         return ch
159 }
160
161 func parseGroupReply(reply udpapi.APIReply) *Group {
162         parts := strings.Split(reply.Lines()[1], "|")
163         ints := make([]int64, len(parts))
164         for i := range parts {
165                 ints[i], _ = strconv.ParseInt(parts[i], 10, 32)
166         }
167
168         irc := ""
169         if parts[7] != "" {
170                 irc = "irc://" + parts[8] + "/" + parts[7][1:]
171         }
172
173         pic := ""
174         if parts[10] != "" {
175                 pic = httpapi.AniDBImageBaseURL + parts[10]
176         }
177
178         rellist := strings.Split(parts[16], "'")
179         relations := make(map[GID]GroupRelationType, len(rellist))
180         for _, rel := range rellist {
181                 r := strings.Split(rel, ",")
182                 if len(r) < 2 {
183                         continue
184                 }
185                 gid, _ := strconv.ParseInt(r[0], 10, 32)
186                 typ, _ := strconv.ParseInt(r[1], 10, 32)
187
188                 relations[GID(gid)] = GroupRelationType(typ)
189         }
190
191         ft := time.Unix(ints[11], 0)
192         if ints[11] == 0 {
193                 ft = time.Time{}
194         }
195         dt := time.Unix(ints[12], 0)
196         if ints[12] == 0 {
197                 dt = time.Time{}
198         }
199         lr := time.Unix(ints[14], 0)
200         if ints[14] == 0 {
201                 lr = time.Time{}
202         }
203         la := time.Unix(ints[15], 0)
204         if ints[15] == 0 {
205                 la = time.Time{}
206         }
207
208         return &Group{
209                 GID: GID(ints[0]),
210
211                 Name:      parts[5],
212                 ShortName: parts[6],
213
214                 IRC:     irc,
215                 URL:     parts[9],
216                 Picture: pic,
217
218                 Founded:   ft,
219                 Disbanded: dt,
220                 // ignore ints[13]
221                 LastRelease:  lr,
222                 LastActivity: la,
223
224                 Rating: Rating{
225                         Rating:    float32(ints[1]) / 100,
226                         VoteCount: int(ints[2]),
227                 },
228                 AnimeCount: int(ints[3]),
229                 FileCount:  int(ints[4]),
230
231                 RelatedGroups: relations,
232
233                 Cached: time.Now(),
234         }
235 }