"encoding/gob"
"strconv"
"strings"
- "sync"
"time"
)
return time.Now().Sub(e.Cached) > EpisodeCacheDuration
}
-var eidAidMap = map[EID]AID{}
-var eidAidLock = sync.RWMutex{}
-
// Unique Episode IDentifier.
type EID int
// Retrieves the Episode corresponding to this EID from the cache.
func (eid EID) Episode() *Episode {
- e, _ := caches.Get(episodeCache).Get(int(eid)).(*Episode)
- return e
+ var e Episode
+ if cache.Get(&e, "eid", eid) == nil {
+ return &e
+ }
+ return nil
}
func cacheEpisode(ep *Episode) {
- eidAidLock.Lock()
- defer eidAidLock.Unlock()
-
- eidAidMap[ep.EID] = ep.AID
- caches.Get(episodeCache).Set(int(ep.EID), ep)
+ cache.Set(ep.AID, "aid", "by-eid", ep.EID)
+ cache.Set(ep, "eid", ep.EID)
}
-// Retrieves the Episode from the cache if possible.
+// Retrieves an Episode by its EID.
//
-// If the result is stale, then queries the UDP API to
-// know which AID owns this EID, then gets the episodes
-// from the Anime.
+// If we know which AID owns this EID, then it's equivalent
+// to an Anime query. Otherwise, uses both the HTTP and UDP
+// APIs to retrieve it.
func (adb *AniDB) EpisodeByID(eid EID) <-chan *Episode {
+ keys := []cacheKey{"eid", eid}
ch := make(chan *Episode, 1)
- if e := eid.Episode(); e != nil && !e.IsStale() {
- ch <- e
+ if eid < 1 {
+ ch <- nil
close(ch)
return ch
}
- ec := caches.Get(episodeCache)
ic := make(chan Cacheable, 1)
go func() { ch <- (<-ic).(*Episode); close(ch) }()
- if ec.Intent(int(eid), ic) {
+ if intentMap.Intent(ic, keys...) {
+ return ch
+ }
+
+ if !cache.CheckValid(keys...) {
+ intentMap.NotifyClose((*Episode)(nil), keys...)
+ return ch
+ }
+
+ e := eid.Episode()
+ if !e.IsStale() {
+ intentMap.NotifyClose(e, keys...)
return ch
}
// The UDP API data is worse than the HTTP API anime data,
// try and get from the corresponding Anime
- eidAidLock.RLock()
- aid, ok := eidAidMap[eid]
- eidAidLock.RUnlock()
+ aid := AID(0)
+ ok := cache.Get(&aid, "aid", "by-eid", eid) == nil
udpDone := false
- var e *Episode
for i := 0; i < 2; i++ {
if !ok && udpDone {
// couldn't get anime and we already ran the EPISODE query
if id, err := strconv.ParseInt(parts[1], 10, 32); err == nil {
ok = true
aid = AID(id)
+ } else {
+ break
}
+ } else if reply.Code() == 340 {
+ cache.MarkInvalid(keys...)
+ cache.Delete(keys...) // deleted EID?
+ break
} else {
break
}
udpDone = true
}
- <-adb.AnimeByID(AID(aid)) // this caches episodes...
- e = eid.Episode() // ...so this is now a cache hit
+ a := <-adb.AnimeByID(AID(aid)) // this caches episodes...
+ ep := eid.Episode() // ...so this is now a cache hit
- if e != nil {
+ if !ep.IsStale() {
+ e = ep
break
} else {
- // if this is somehow still a miss, then the EID<->AID map broke
- eidAidLock.Lock()
- delete(eidAidMap, eid)
- eidAidLock.Unlock()
+ // check to see if we looked in the right AID
+ found := false
+ if a != nil {
+ for _, ep := range a.Episodes {
+ if eid == ep.EID {
+ found = true
+ break
+ }
+ }
+ }
- ok = false
+ // if found, then it's just that the anime is also stale (offline?)
+ if found {
+ break
+ } else {
+ // otherwise, the EID<->AID map broke
+ ok = false
+ cache.Delete("aid", "by-eid", eid)
+ }
}
}
- // Caching (and channel broadcasting) done by AnimeByID
+ intentMap.NotifyClose(e, keys...)
}()
return ch
}