X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=animecache.go;h=eff4df677e73928339bfee4caccd81bdafda0bcb;hb=d5fc5a1b93c9361c08d181a1cb4dca447e9edf84;hp=ad44bfa6f3ebef7d39ec8578d67ca91dc5c6493c;hpb=59218ebf4cf9c2353dfa2cb416ee7d2542a3b9b9;p=go-anidb.git diff --git a/animecache.go b/animecache.go index ad44bfa..eff4df6 100644 --- a/animecache.go +++ b/animecache.go @@ -1,30 +1,38 @@ package anidb import ( - "encoding/gob" "fmt" "github.com/Kovensky/go-anidb/http" "github.com/Kovensky/go-anidb/misc" "github.com/Kovensky/go-anidb/udp" + "github.com/Kovensky/go-fscache" "sort" "strconv" "strings" "time" ) -func init() { - gob.RegisterName("*github.com/Kovensky/go-anidb.Anime", &Anime{}) -} +var _ cacheable = &Anime{} -func (a *Anime) Touch() { - a.Cached = time.Now() +func (a *Anime) setCachedTS(ts time.Time) { + a.Cached = ts } func (a *Anime) IsStale() bool { if a == nil { return true } - return time.Now().Sub(a.Cached) > AnimeCacheDuration + now := time.Now() + diff := now.Sub(a.Cached) + if a.Incomplete { + return diff > AnimeIncompleteCacheDuration + } + + // If the anime ended, and more than AnimeCacheDuration time ago at that + if !a.EndDate.IsZero() && now.After(a.EndDate.Add(AnimeCacheDuration)) { + return diff > FinishedAnimeCacheDuration + } + return diff > AnimeCacheDuration } // Unique Anime IDentifier. @@ -32,8 +40,11 @@ type AID int // Returns a cached Anime. Returns nil if there is no cached Anime with this AID. func (aid AID) Anime() *Anime { - a, _ := caches.Get(animeCache).Get(int(aid)).(*Anime) - return a + var a Anime + if CacheGet(&a, "aid", aid) == nil { + return &a + } + return nil } type httpAnimeResponse struct { @@ -41,31 +52,38 @@ type httpAnimeResponse struct { err error } -// Retrieves an Anime from the cache if possible. If it isn't cached, -// or if the cache is stale, queries both the UDP and HTTP APIs -// for data. -// -// Note: This can take at least 4 seconds during heavy traffic. +// Retrieves an Anime by its AID. Uses both the HTTP and UDP APIs, +// but can work without the UDP API. func (adb *AniDB) AnimeByID(aid AID) <-chan *Anime { + key := []fscache.CacheKey{"aid", aid} ch := make(chan *Anime, 1) - anime := aid.Anime() - if !anime.IsStale() { - ch <- anime + if aid < 1 { + ch <- nil close(ch) - return ch } - ac := caches.Get(animeCache) - ic := make(chan Cacheable, 1) + ic := make(chan notification, 1) go func() { ch <- (<-ic).(*Anime); close(ch) }() - if ac.Intent(int(aid), ic) { + if intentMap.Intent(ic, key...) { + return ch + } + + if !Cache.IsValid(InvalidKeyCacheDuration, key...) { + intentMap.NotifyClose((*Anime)(nil), key...) + return ch + } + + anime := aid.Anime() + if !anime.IsStale() { + intentMap.NotifyClose(anime, key...) return ch } go func() { httpChan := make(chan httpAnimeResponse, 1) go func() { + adb.Logger.Printf("HTTP>>> Anime %d", aid) a, err := httpapi.GetAnime(int(aid)) httpChan <- httpAnimeResponse{anime: a, err: err} }() @@ -88,40 +106,70 @@ func (adb *AniDB) AnimeByID(aid AID) <-chan *Anime { for i := 0; i < 2; i++ { select { case <-timeout: - ok = false + // HTTP API timeout + if httpChan != nil { + adb.Logger.Printf("HTTP<<< Timeout") + close(httpChan) + } case resp := <-httpChan: if resp.err != nil { + adb.Logger.Printf("HTTP<<< %v", resp.err) ok = false break Loop } - if a := anime.populateFromHTTP(resp.anime); a == nil { + + if resp.anime.Error != "" { + adb.Logger.Printf("HTTP<<< Error %q", resp.anime.Error) + } + + if anime.populateFromHTTP(resp.anime) { + adb.Logger.Printf("HTTP<<< Anime %q", anime.PrimaryTitle) + } else { + // HTTP ok but parsing not ok + if anime.PrimaryTitle == "" { + Cache.SetInvalid(key...) + } + + switch resp.anime.Error { + case "Anime not found", "aid Missing or Invalid": + // deleted AID? + Cache.Delete(key...) + } + ok = false break Loop - } else { - anime = a } + httpChan = nil case reply := <-udpChan: - anime.Incomplete = !anime.populateFromUDP(reply) + if reply.Code() == 330 { + Cache.SetInvalid(key...) + // deleted AID? + Cache.Delete(key...) + + ok = false + break Loop + } else { + anime.Incomplete = !anime.populateFromUDP(reply) + } udpChan = nil } } if anime.PrimaryTitle != "" { if ok { - ac.Set(int(aid), anime) - } else { - ac.Flush(int(aid), anime) + CacheSet(anime, key...) } + intentMap.NotifyClose(anime, key...) } else { - ac.Set(int(aid), (*Anime)(nil)) + intentMap.NotifyClose((*Anime)(nil), key...) } }() return ch } -func (a *Anime) populateFromHTTP(reply httpapi.Anime) *Anime { +func (a *Anime) populateFromHTTP(reply httpapi.Anime) bool { if reply.Error != "" { - return (*Anime)(nil) + return false } if a.AID != AID(reply.ID) { @@ -145,12 +193,6 @@ func (a *Anime) populateFromHTTP(reply httpapi.Anime) *Anime { for _, title := range reply.Titles { switch title.Type { case "main": - if a.PrimaryTitle != "" { - // We assume there's only ever one "main" title - panic( - fmt.Sprintf("PrimaryTitle %q already set, new PrimaryTitle %q received!", - a.PrimaryTitle, title.Title)) - } a.PrimaryTitle = title.Title case "official": if a.OfficialTitles == nil { @@ -203,7 +245,7 @@ func (a *Anime) populateFromHTTP(reply httpapi.Anime) *Anime { titles[Language(title.Lang)] = title.Title } - e := Episode{ + e := &Episode{ EID: EID(ep.ID), AID: a.AID, @@ -219,12 +261,12 @@ func (a *Anime) populateFromHTTP(reply httpapi.Anime) *Anime { Titles: titles, } counts[e.Type]++ - cacheEpisode(&e) + cacheEpisode(e) a.Episodes = append(a.Episodes, e) } - a.EpisodeCount = EpisodeCount{ + a.EpisodeCount = misc.EpisodeCount{ RegularCount: counts[misc.EpisodeTypeRegular], SpecialCount: counts[misc.EpisodeTypeSpecial], CreditsCount: counts[misc.EpisodeTypeCredits], @@ -239,7 +281,7 @@ func (a *Anime) populateFromHTTP(reply httpapi.Anime) *Anime { } } - return a + return true } func (a *Anime) populateResources(list []httpapi.Resource) {