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