X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=groupcache.go;h=8d0f92cfd5f7c41f39d48fa30297e0b25bde6ef9;hb=e6b6a718a328e339e022cc05f5c79940cca6dec5;hp=e67cd59943640e4e4ad098871bbc748fc59b03c6;hpb=e54415863fb1aaa9a12d4a351557667f2d668c30;p=go-anidb.git diff --git a/groupcache.go b/groupcache.go index e67cd59..8d0f92c 100644 --- a/groupcache.go +++ b/groupcache.go @@ -3,6 +3,7 @@ package anidb import ( "encoding/gob" "github.com/Kovensky/go-anidb/http" + "github.com/Kovensky/go-anidb/udp" "strconv" "strings" "time" @@ -10,6 +11,8 @@ import ( func init() { gob.RegisterName("*github.com/Kovensky/go-anidb.Group", &Group{}) + gob.RegisterName("github.com/Kovensky/go-anidb.GID", GID(0)) + gob.RegisterName("*github.com/Kovensky/go-anidb.gidCache", &gidCache{}) } func (g *Group) Touch() { @@ -26,6 +29,11 @@ func (g *Group) IsStale() bool { // Unique Group IDentifier type GID int +// make GID cacheable + +func (e GID) Touch() {} +func (e GID) IsStale() bool { return false } + // Retrieves the Group from the cache. func (gid GID) Group() *Group { var g Group @@ -35,22 +43,44 @@ func (gid GID) Group() *Group { return nil } -// Returns a Group from the cache if possible. -// -// If the Group is stale, then retrieves the Group -// through the UDP API. +type gidCache struct { + GID + Time time.Time +} + +func (c *gidCache) Touch() { c.Time = time.Now() } +func (c *gidCache) IsStale() bool { + if c != nil && time.Now().Sub(c.Time) < GroupCacheDuration { + return false + } + return true +} + +// Retrieves a Group by its GID. Uses the UDP API. func (adb *AniDB) GroupByID(gid GID) <-chan *Group { keys := []cacheKey{"gid", gid} ch := make(chan *Group, 1) + if gid < 1 { + ch <- nil + close(ch) + return ch + } + ic := make(chan Cacheable, 1) go func() { ch <- (<-ic).(*Group); close(ch) }() if intentMap.Intent(ic, keys...) { return ch } - if g := gid.Group(); !g.IsStale() { - intentMap.Notify(g, keys...) + if !cache.CheckValid(keys...) { + intentMap.NotifyClose((*Group)(nil), keys...) + return ch + } + + g := gid.Group() + if !g.IsStale() { + intentMap.NotifyClose(g, keys...) return ch } @@ -58,81 +88,165 @@ func (adb *AniDB) GroupByID(gid GID) <-chan *Group { reply := <-adb.udp.SendRecv("GROUP", paramMap{"gid": gid}) + if reply.Error() == nil { + g = parseGroupReply(reply) + + cache.Set(&gidCache{GID: g.GID}, "gid", "by-name", g.Name) + cache.Set(&gidCache{GID: g.GID}, "gid", "by-shortname", g.ShortName) + cache.Set(g, keys...) + } else if reply.Code() == 350 { + cache.MarkInvalid(keys...) + cache.Delete(keys...) // deleted group? + } + + intentMap.NotifyClose(g, keys...) + }() + return ch +} + +// Retrieves a Group by its name. Either full or short names are matched. +// Uses the UDP API. +func (adb *AniDB) GroupByName(gname string) <-chan *Group { + keys := []cacheKey{"gid", "by-name", gname} + altKeys := []cacheKey{"gid", "by-shortname", gname} + ch := make(chan *Group, 1) + + if gname == "" { + ch <- nil + close(ch) + return ch + } + + ic := make(chan Cacheable, 1) + go func() { + gid := (<-ic).(GID) + if gid > 0 { + ch <- <-adb.GroupByID(gid) + } + close(ch) + }() + if intentMap.Intent(ic, keys...) { + return ch + } + + if !cache.CheckValid(keys...) { + intentMap.NotifyClose(GID(0), keys...) + return ch + } + + gid := GID(0) + + var gc gidCache + if cache.Get(&gc, keys...) == nil && !gc.IsStale() { + intentMap.NotifyClose(gc.GID, keys...) + return ch + } + gid = gc.GID + + if gid == 0 { + if cache.Get(&gc, altKeys...) == nil && !gc.IsStale() { + intentMap.NotifyClose(gc.GID, keys...) + return ch + } + gid = gc.GID + } + + go func() { + reply := <-adb.udp.SendRecv("GROUP", + paramMap{"gname": gname}) + var g *Group if reply.Error() == nil { - parts := strings.Split(reply.Lines()[1], "|") - ints := make([]int64, len(parts)) - for i := range parts { - ints[i], _ = strconv.ParseInt(parts[i], 10, 32) - } - - irc := "" - if parts[7] != "" { - irc = "irc://" + parts[8] + "/" + parts[7][1:] - } - - pic := "" - if parts[10] != "" { - pic = httpapi.AniDBImageBaseURL + parts[10] - } - - rellist := strings.Split(parts[16], "'") - relations := make(map[GID]GroupRelationType, len(rellist)) - for _, rel := range rellist { - r := strings.Split(rel, ",") - gid, _ := strconv.ParseInt(r[0], 10, 32) - typ, _ := strconv.ParseInt(r[1], 10, 32) - - relations[GID(gid)] = GroupRelationType(typ) - } - - ft := time.Unix(ints[11], 0) - if ints[11] == 0 { - ft = time.Time{} - } - dt := time.Unix(ints[12], 0) - if ints[12] == 0 { - dt = time.Time{} - } - lr := time.Unix(ints[14], 0) - if ints[14] == 0 { - lr = time.Time{} - } - la := time.Unix(ints[15], 0) - if ints[15] == 0 { - la = time.Time{} - } - - g = &Group{ - GID: GID(ints[0]), - - Name: parts[5], - ShortName: parts[6], - - IRC: irc, - URL: parts[9], - Picture: pic, - - Founded: ft, - Disbanded: dt, - // ignore ints[13] - LastRelease: lr, - LastActivity: la, - - Rating: Rating{ - Rating: float32(ints[1]) / 100, - VoteCount: int(ints[2]), - }, - AnimeCount: int(ints[3]), - FileCount: int(ints[4]), - - RelatedGroups: relations, - - Cached: time.Now(), - } + g = parseGroupReply(reply) + + gid = g.GID + + cache.Set(&gidCache{GID: gid}, keys...) + cache.Set(&gidCache{GID: gid}, altKeys...) + cache.Set(g, "gid", gid) + } else if reply.Code() == 350 { + cache.MarkInvalid(keys...) + cache.Delete(keys...) // renamed group? + cache.Delete(altKeys...) } - cache.Set(g, keys...) - intentMap.Notify(g, keys...) + + intentMap.NotifyClose(gid, keys...) }() return ch } + +func parseGroupReply(reply udpapi.APIReply) *Group { + parts := strings.Split(reply.Lines()[1], "|") + ints := make([]int64, len(parts)) + for i := range parts { + ints[i], _ = strconv.ParseInt(parts[i], 10, 32) + } + + irc := "" + if parts[7] != "" { + irc = "irc://" + parts[8] + "/" + parts[7][1:] + } + + pic := "" + if parts[10] != "" { + pic = httpapi.AniDBImageBaseURL + parts[10] + } + + rellist := strings.Split(parts[16], "'") + relations := make(map[GID]GroupRelationType, len(rellist)) + for _, rel := range rellist { + r := strings.Split(rel, ",") + if len(r) < 2 { + continue + } + gid, _ := strconv.ParseInt(r[0], 10, 32) + typ, _ := strconv.ParseInt(r[1], 10, 32) + + relations[GID(gid)] = GroupRelationType(typ) + } + + ft := time.Unix(ints[11], 0) + if ints[11] == 0 { + ft = time.Time{} + } + dt := time.Unix(ints[12], 0) + if ints[12] == 0 { + dt = time.Time{} + } + lr := time.Unix(ints[14], 0) + if ints[14] == 0 { + lr = time.Time{} + } + la := time.Unix(ints[15], 0) + if ints[15] == 0 { + la = time.Time{} + } + + return &Group{ + GID: GID(ints[0]), + + Name: parts[5], + ShortName: parts[6], + + IRC: irc, + URL: parts[9], + Picture: pic, + + Founded: ft, + Disbanded: dt, + // ignore ints[13] + LastRelease: lr, + LastActivity: la, + + Rating: Rating{ + Rating: float32(ints[1]) / 100, + VoteCount: int(ints[2]), + }, + AnimeCount: int(ints[3]), + FileCount: int(ints[4]), + + RelatedGroups: relations, + + Cached: time.Now(), + } +}