]> git.lizzy.rs Git - go-anidb.git/blob - mylistcache.go
anidb: Update the MyListAnime when running a mylist file query
[go-anidb.git] / mylistcache.go
1 package anidb
2
3 import (
4         "github.com/Kovensky/go-anidb/udp"
5         "github.com/Kovensky/go-fscache"
6         "strconv"
7         "strings"
8         "time"
9 )
10
11 func (e *MyListEntry) setCachedTS(ts time.Time) {
12         e.Cached = ts
13 }
14
15 func (e *MyListEntry) IsStale() bool {
16         if e == nil {
17                 return true
18         }
19
20         max := MyListCacheDuration
21         if !e.DateWatched.IsZero() {
22                 max = MyListWatchedCacheDuration
23         }
24         return time.Now().Sub(e.Cached) > max
25 }
26
27 var _ cacheable = &MyListEntry{}
28
29 func (lid LID) MyListEntry() *MyListEntry {
30         var e MyListEntry
31         if CacheGet(&e, "lid", lid) == nil {
32                 return &e
33         }
34         return nil
35 }
36
37 func (adb *AniDB) MyListByFile(f *File) <-chan *MyListEntry {
38         ch := make(chan *MyListEntry, 1)
39
40         if f == nil {
41                 ch <- nil
42                 close(ch)
43                 return ch
44         }
45
46         go func() {
47                 user := <-adb.GetCurrentUser()
48
49                 var entry *MyListEntry
50
51                 if lid := f.LID[user.UID]; lid != 0 {
52                         entry = <-adb.MyListByLID(lid)
53                 }
54                 if entry == nil {
55                         entry = <-adb.MyListByFID(f.FID)
56                 }
57                 ch <- entry
58                 close(ch)
59         }()
60         return ch
61 }
62
63 func (adb *AniDB) MyListByLID(lid LID) <-chan *MyListEntry {
64         key := []fscache.CacheKey{"mylist", lid}
65         ch := make(chan *MyListEntry, 1)
66
67         if lid < 1 {
68                 ch <- nil
69                 close(ch)
70                 return ch
71         }
72
73         ic := make(chan notification, 1)
74         go func() { ch <- (<-ic).(*MyListEntry); close(ch) }()
75         if intentMap.Intent(ic, key...) {
76                 return ch
77         }
78
79         if !Cache.IsValid(InvalidKeyCacheDuration, key...) {
80                 intentMap.NotifyClose((*MyListEntry)(nil), key...)
81                 return ch
82         }
83
84         entry := lid.MyListEntry()
85         if !entry.IsStale() {
86                 intentMap.NotifyClose(entry, key...)
87                 return ch
88         }
89
90         go func() {
91                 reply := <-adb.udp.SendRecv("MYLIST", paramMap{"lid": lid})
92
93                 switch reply.Code() {
94                 case 221:
95                         entry = adb.parseMylistReply(reply) // caches
96                 case 312:
97                         panic("Multiple MYLIST entries when querying for single LID")
98                 case 321:
99                         Cache.SetInvalid(key...)
100                 }
101
102                 intentMap.NotifyClose(entry, key...)
103         }()
104         return ch
105 }
106
107 func (adb *AniDB) MyListByFID(fid FID) <-chan *MyListEntry {
108         ch := make(chan *MyListEntry, 1)
109
110         if fid < 1 {
111                 ch <- nil
112                 close(ch)
113                 return ch
114         }
115
116         // This is an odd one: we lack enough data at first to create the cache key
117         go func() {
118                 user := <-adb.GetCurrentUser()
119                 if user == nil || user.UID < 1 {
120                         ch <- nil
121                         close(ch)
122                         return
123                 }
124
125                 key := []fscache.CacheKey{"mylist", "by-fid", fid, user.UID}
126
127                 ic := make(chan notification, 1)
128                 go func() { ch <- (<-ic).(*MyListEntry); close(ch) }()
129                 if intentMap.Intent(ic, key...) {
130                         return
131                 }
132
133                 if !Cache.IsValid(InvalidKeyCacheDuration, key...) {
134                         intentMap.NotifyClose((*MyListEntry)(nil), key...)
135                         return
136                 }
137
138                 lid := LID(0)
139                 switch ts, err := Cache.Get(&lid, key...); {
140                 case err == nil && time.Now().Sub(ts) < LIDCacheDuration:
141                         intentMap.NotifyClose(<-adb.MyListByLID(lid), key...)
142                         return
143                 }
144
145                 reply := <-adb.udp.SendRecv("MYLIST", paramMap{"fid": fid})
146
147                 var entry *MyListEntry
148
149                 switch reply.Code() {
150                 case 221:
151                         entry = adb.parseMylistReply(reply) // caches
152                 case 312:
153                         panic("Multiple MYLIST entries when querying for single FID")
154                 case 321:
155                         Cache.SetInvalid(key...)
156                 }
157
158                 intentMap.NotifyClose(entry, key...)
159         }()
160         return ch
161 }
162
163 func (adb *AniDB) parseMylistReply(reply udpapi.APIReply) *MyListEntry {
164         if reply.Error() != nil {
165                 return nil
166         }
167
168         parts := strings.Split(reply.Lines()[1], "|")
169         ints := make([]int64, len(parts))
170         for i := range parts {
171                 ints[i], _ = strconv.ParseInt(parts[i], 10, 64)
172         }
173
174         da := time.Unix(ints[5], 0)
175         if ints[5] == 0 {
176                 da = time.Time{}
177         }
178         dw := time.Unix(ints[7], 0)
179         if ints[7] == 0 {
180                 dw = time.Time{}
181         }
182
183         e := &MyListEntry{
184                 LID: LID(ints[0]),
185
186                 FID: FID(ints[1]),
187                 EID: EID(ints[2]),
188                 AID: AID(ints[3]),
189                 GID: GID(ints[4]),
190
191                 DateAdded:   da,
192                 DateWatched: dw,
193
194                 State:       FileState(ints[11]),
195                 MyListState: MyListState(ints[6]),
196
197                 Storage: parts[8],
198                 Source:  parts[9],
199                 Other:   parts[10],
200         }
201
202         user := <-adb.GetCurrentUser()
203
204         if user != nil {
205                 if f := e.FID.File(); f != nil {
206                         f.LID[user.UID] = e.LID
207                         cacheFile(f)
208
209                         now := time.Now()
210                         mla := <-adb.MyListAnime(f.AID)
211
212                         key := []fscache.CacheKey{"mylist-anime", user.UID, f.AID}
213
214                         intentMap.Intent(nil, key...)
215
216                         if mla == nil {
217                                 mla = &MyListAnime{}
218                         }
219
220                         if mla.Cached.Before(now) {
221                                 el := mla.EpisodesWithState[e.MyListState]
222                                 el.Add(f.EpisodeNumber)
223                                 mla.EpisodesWithState[e.MyListState] = el
224
225                                 if e.DateWatched.IsZero() {
226                                         mla.WatchedEpisodes.Sub(f.EpisodeNumber)
227                                 } else {
228                                         mla.WatchedEpisodes.Add(f.EpisodeNumber)
229                                 }
230
231                                 eg := mla.EpisodesPerGroup[f.GID]
232                                 eg.Add(f.EpisodeNumber)
233                                 mla.EpisodesPerGroup[f.GID] = eg
234
235                                 if mla.Cached.IsZero() {
236                                         // as attractive as such an ancient mtime would be,
237                                         // few filesystems can represent it; just make it old enough
238                                         mla.Cached = now.Add(-2 * MyListCacheDuration)
239                                 }
240
241                                 Cache.Set(mla, key...)
242                                 Cache.Chtime(mla.Cached, key...)
243                         }
244
245                         // this unfortunately races if Intent returns true:
246                         // only the first NotifyClose call actually notifies
247                         go intentMap.NotifyClose(mla, key...)
248                 }
249
250                 CacheSet(e, "mylist", "by-fid", e.FID, user.UID)
251         }
252
253         CacheSet(e, "mylist", e.LID)
254
255         return e
256 }