]> git.lizzy.rs Git - go-anidb.git/blob - groupcache.go
misc: CountEpisodes and convenience conversion functions
[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 // Retrieves a Group by its GID. Uses the UDP API.
60 func (adb *AniDB) GroupByID(gid GID) <-chan *Group {
61         keys := []cacheKey{"gid", gid}
62         ch := make(chan *Group, 1)
63
64         if gid < 1 {
65                 ch <- nil
66                 close(ch)
67                 return ch
68         }
69
70         ic := make(chan Cacheable, 1)
71         go func() { ch <- (<-ic).(*Group); close(ch) }()
72         if intentMap.Intent(ic, keys...) {
73                 return ch
74         }
75
76         if !cache.CheckValid(keys...) {
77                 intentMap.NotifyClose((*Group)(nil), keys...)
78                 return ch
79         }
80
81         g := gid.Group()
82         if !g.IsStale() {
83                 intentMap.NotifyClose(g, keys...)
84                 return ch
85         }
86
87         go func() {
88                 reply := <-adb.udp.SendRecv("GROUP",
89                         paramMap{"gid": gid})
90
91                 if reply.Error() == nil {
92                         g = parseGroupReply(reply)
93
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                 } else if reply.Code() == 350 {
98                         cache.MarkInvalid(keys...)
99                         cache.Delete(keys...) // deleted group?
100                 }
101
102                 intentMap.NotifyClose(g, keys...)
103         }()
104         return ch
105 }
106
107 // Retrieves a Group by its name. Either full or short names are matched.
108 // Uses the UDP API.
109 func (adb *AniDB) GroupByName(gname string) <-chan *Group {
110         keys := []cacheKey{"gid", "by-name", gname}
111         altKeys := []cacheKey{"gid", "by-shortname", gname}
112         ch := make(chan *Group, 1)
113
114         if gname == "" {
115                 ch <- nil
116                 close(ch)
117                 return ch
118         }
119
120         ic := make(chan Cacheable, 1)
121         go func() {
122                 gid := (<-ic).(GID)
123                 if gid > 0 {
124                         ch <- <-adb.GroupByID(gid)
125                 }
126                 close(ch)
127         }()
128         if intentMap.Intent(ic, keys...) {
129                 return ch
130         }
131
132         if !cache.CheckValid(keys...) {
133                 intentMap.NotifyClose(GID(0), keys...)
134                 return ch
135         }
136
137         gid := GID(0)
138
139         var gc gidCache
140         if cache.Get(&gc, keys...) == nil && !gc.IsStale() {
141                 intentMap.NotifyClose(gc.GID, keys...)
142                 return ch
143         }
144         gid = gc.GID
145
146         if gid == 0 {
147                 if cache.Get(&gc, altKeys...) == nil && !gc.IsStale() {
148                         intentMap.NotifyClose(gc.GID, keys...)
149                         return ch
150                 }
151                 gid = gc.GID
152         }
153
154         go func() {
155                 reply := <-adb.udp.SendRecv("GROUP",
156                         paramMap{"gname": gname})
157
158                 var g *Group
159                 if reply.Error() == nil {
160                         g = parseGroupReply(reply)
161
162                         gid = g.GID
163
164                         cache.Set(&gidCache{GID: gid}, keys...)
165                         cache.Set(&gidCache{GID: gid}, altKeys...)
166                         cache.Set(g, "gid", gid)
167                 } else if reply.Code() == 350 {
168                         cache.MarkInvalid(keys...)
169                         cache.Delete(keys...) // renamed group?
170                         cache.Delete(altKeys...)
171                 }
172
173                 intentMap.NotifyClose(gid, keys...)
174         }()
175         return ch
176 }
177
178 func parseGroupReply(reply udpapi.APIReply) *Group {
179         parts := strings.Split(reply.Lines()[1], "|")
180         ints := make([]int64, len(parts))
181         for i := range parts {
182                 ints[i], _ = strconv.ParseInt(parts[i], 10, 32)
183         }
184
185         irc := ""
186         if parts[7] != "" {
187                 irc = "irc://" + parts[8] + "/" + parts[7][1:]
188         }
189
190         pic := ""
191         if parts[10] != "" {
192                 pic = httpapi.AniDBImageBaseURL + parts[10]
193         }
194
195         rellist := strings.Split(parts[16], "'")
196         relations := make(map[GID]GroupRelationType, len(rellist))
197         for _, rel := range rellist {
198                 r := strings.Split(rel, ",")
199                 if len(r) < 2 {
200                         continue
201                 }
202                 gid, _ := strconv.ParseInt(r[0], 10, 32)
203                 typ, _ := strconv.ParseInt(r[1], 10, 32)
204
205                 relations[GID(gid)] = GroupRelationType(typ)
206         }
207
208         ft := time.Unix(ints[11], 0)
209         if ints[11] == 0 {
210                 ft = time.Time{}
211         }
212         dt := time.Unix(ints[12], 0)
213         if ints[12] == 0 {
214                 dt = time.Time{}
215         }
216         lr := time.Unix(ints[14], 0)
217         if ints[14] == 0 {
218                 lr = time.Time{}
219         }
220         la := time.Unix(ints[15], 0)
221         if ints[15] == 0 {
222                 la = time.Time{}
223         }
224
225         return &Group{
226                 GID: GID(ints[0]),
227
228                 Name:      parts[5],
229                 ShortName: parts[6],
230
231                 IRC:     irc,
232                 URL:     parts[9],
233                 Picture: pic,
234
235                 Founded:   ft,
236                 Disbanded: dt,
237                 // ignore ints[13]
238                 LastRelease:  lr,
239                 LastActivity: la,
240
241                 Rating: Rating{
242                         Rating:    float32(ints[1]) / 100,
243                         VoteCount: int(ints[2]),
244                 },
245                 AnimeCount: int(ints[3]),
246                 FileCount:  int(ints[4]),
247
248                 RelatedGroups: relations,
249
250                 Cached: time.Now(),
251         }
252 }