]> git.lizzy.rs Git - go-anidb.git/blob - episodecache.go
anidb: Immediately return error for clearly invalid IDs
[go-anidb.git] / episodecache.go
1 package anidb
2
3 import (
4         "encoding/gob"
5         "strconv"
6         "strings"
7         "time"
8 )
9
10 func init() {
11         gob.RegisterName("*github.com/Kovensky/go-anidb.Episode", &Episode{})
12 }
13
14 func (e *Episode) Touch() {
15         e.Cached = time.Now()
16 }
17
18 func (e *Episode) IsStale() bool {
19         if e == nil {
20                 return true
21         }
22         return time.Now().Sub(e.Cached) > EpisodeCacheDuration
23 }
24
25 // Unique Episode IDentifier.
26 type EID int
27
28 // Retrieves the Episode corresponding to this EID from the cache.
29 func (eid EID) Episode() *Episode {
30         var e Episode
31         if cache.Get(&e, "eid", eid) == nil {
32                 return &e
33         }
34         return nil
35 }
36
37 func cacheEpisode(ep *Episode) {
38         cache.Set(ep.AID, "aid", "by-eid", ep.EID)
39         cache.Set(ep, "eid", ep.EID)
40 }
41
42 // Retrieves an Episode by its EID.
43 //
44 // If we know which AID owns this EID, then it's equivalent
45 // to an Anime query. Otherwise, uses both the HTTP and UDP
46 // APIs to retrieve it.
47 func (adb *AniDB) EpisodeByID(eid EID) <-chan *Episode {
48         keys := []cacheKey{"eid", eid}
49         ch := make(chan *Episode, 1)
50
51         if eid < 1 {
52                 ch <- nil
53                 close(ch)
54                 return ch
55         }
56
57         ic := make(chan Cacheable, 1)
58         go func() { ch <- (<-ic).(*Episode); close(ch) }()
59         if intentMap.Intent(ic, keys...) {
60                 return ch
61         }
62
63         if !cache.CheckValid(keys...) {
64                 intentMap.Notify((*Episode)(nil), keys...)
65                 return ch
66         }
67
68         e := eid.Episode()
69         if !e.IsStale() {
70                 intentMap.Notify(e, keys...)
71                 return ch
72         }
73
74         go func() {
75                 // The UDP API data is worse than the HTTP API anime data,
76                 // try and get from the corresponding Anime
77
78                 aid := AID(0)
79                 ok := cache.Get(&aid, "aid", "by-eid", eid) == nil
80
81                 udpDone := false
82
83                 for i := 0; i < 2; i++ {
84                         if !ok && udpDone {
85                                 // couldn't get anime and we already ran the EPISODE query
86                                 break
87                         }
88
89                         if !ok {
90                                 // We don't know what the AID is yet.
91                                 reply := <-adb.udp.SendRecv("EPISODE", paramMap{"eid": eid})
92
93                                 if reply.Error() == nil {
94                                         parts := strings.Split(reply.Lines()[1], "|")
95
96                                         if id, err := strconv.ParseInt(parts[1], 10, 32); err == nil {
97                                                 ok = true
98                                                 aid = AID(id)
99                                         } else {
100                                                 break
101                                         }
102                                 } else if reply.Code() == 340 {
103                                         cache.MarkInvalid(keys...)
104                                         cache.Delete(keys...) // deleted EID?
105                                         break
106                                 } else {
107                                         break
108                                 }
109                                 udpDone = true
110                         }
111                         a := <-adb.AnimeByID(AID(aid)) // this caches episodes...
112                         ep := eid.Episode()            // ...so this is now a cache hit
113
114                         if !ep.IsStale() {
115                                 e = ep
116                                 break
117                         } else {
118                                 // check to see if we looked in the right AID
119                                 found := false
120                                 if a != nil {
121                                         for _, ep := range a.Episodes {
122                                                 if eid == ep.EID {
123                                                         found = true
124                                                         break
125                                                 }
126                                         }
127                                 }
128
129                                 // if found, then it's just that the anime is also stale (offline?)
130                                 if found {
131                                         break
132                                 } else {
133                                         // otherwise, the EID<->AID map broke
134                                         ok = false
135                                         cache.Delete("aid", "by-eid", eid)
136                                 }
137                         }
138                 }
139                 intentMap.Notify(e, keys...)
140         }()
141         return ch
142 }