]> git.lizzy.rs Git - go-anidb.git/blob - mylistmanip.go
anidb: Fix crash when MyListAdd/MyListEdit get a nil MyListSet
[go-anidb.git] / mylistmanip.go
1 package anidb
2
3 import (
4         "github.com/Kovensky/go-fscache"
5         "strconv"
6         "time"
7 )
8
9 // These are all pointers because they're not
10 // sent at all if they're nil
11 type MyListSet struct {
12         State    *MyListState
13         Watched  *bool
14         ViewDate *time.Time
15         Source   *string
16         Storage  *string
17         Other    *string
18 }
19
20 func (set *MyListSet) toParamMap() (pm paramMap) {
21         pm = paramMap{}
22         if set == nil {
23                 return
24         }
25
26         if set.State != nil {
27                 pm["state"] = *set.State
28         }
29         if set.Watched != nil {
30                 pm["viewed"] = *set.Watched
31         }
32         if set.ViewDate != nil {
33                 if set.ViewDate.IsZero() {
34                         pm["viewdate"] = 0
35                 } else {
36                         pm["viewdate"] = int(int32(set.ViewDate.Unix()))
37                 }
38         }
39         if set.Source != nil {
40                 pm["source"] = *set.Source
41         }
42         if set.Storage != nil {
43                 pm["storage"] = *set.Storage
44         }
45         if set.Other != nil {
46                 pm["other"] = *set.Other
47         }
48         return
49 }
50
51 func (set *MyListSet) update(uid UID, f *File, lid LID) {
52         if f.LID[uid] != lid {
53                 f.LID[uid] = lid
54                 Cache.Set(f, "fid", f.FID)
55                 Cache.Chtime(f.Cached, "fid", f.FID)
56         }
57
58         mla := uid.MyListAnime(f.AID)
59         if mla == nil {
60                 mla = &MyListAnime{
61                         EpisodesWithState: MyListStateMap{},
62                         EpisodesPerGroup:  GroupEpisodes{},
63                 }
64         }
65         // We only ever add, not remove -- we don't know if other files also satisfy the list
66         eg := mla.EpisodesPerGroup[f.GID]
67         eg.Add(f.EpisodeNumber)
68         mla.EpisodesPerGroup[f.GID] = eg
69
70         if set != nil {
71                 if set.State != nil {
72                         es := mla.EpisodesWithState[*set.State]
73                         es.Add(f.EpisodeNumber)
74                         mla.EpisodesWithState[*set.State] = es
75                 }
76
77                 if set.Watched != nil && *set.Watched ||
78                         set.ViewDate != nil && !set.ViewDate.IsZero() {
79                         mla.WatchedEpisodes.Add(f.EpisodeNumber)
80                 }
81         }
82
83         Cache.Set(mla, "mylist-anime", uid, f.AID)
84         Cache.Chtime(mla.Cached, "mylist-anime", uid, f.AID)
85
86         if set == nil ||
87                 (set.ViewDate == nil && set.Watched == nil && set.State == nil &&
88                         set.Source == nil && set.Storage == nil && set.Other == nil) {
89                 return
90         }
91
92         e := lid.MyListEntry()
93         if set.ViewDate != nil {
94                 e.DateWatched = *set.ViewDate
95         } else if set.Watched != nil {
96                 if *set.Watched {
97                         e.DateWatched = time.Now()
98                 } else {
99                         e.DateWatched = time.Time{}
100                 }
101         }
102         if set.State != nil {
103                 e.MyListState = *set.State
104         }
105         if set.Source != nil {
106                 e.Source = *set.Source
107         }
108         if set.Storage != nil {
109                 e.Storage = *set.Storage
110         }
111         if set.Other != nil {
112                 e.Other = *set.Other
113         }
114         Cache.Set(e, "mylist", lid)
115         Cache.Chtime(e.Cached, "mylist", lid)
116 }
117
118 func (adb *AniDB) MyListAdd(f *File, set *MyListSet) <-chan LID {
119         ch := make(chan LID, 1)
120         if f == nil {
121                 ch <- 0
122                 close(ch)
123                 return ch
124         }
125
126         go func() {
127                 user := <-adb.GetCurrentUser()
128                 if user == nil || user.UID < 1 {
129                         ch <- 0
130                         close(ch)
131                         return
132                 }
133
134                 // for the intent map; doesn't get cached
135                 key := []fscache.CacheKey{"mylist-add", user.UID, f.FID}
136
137                 ic := make(chan notification, 1)
138                 go func() { ch <- (<-ic).(LID); close(ch) }()
139                 if intentMap.Intent(ic, key...) {
140                         return
141                 }
142
143                 pm := set.toParamMap()
144                 pm["fid"] = f.FID
145
146                 reply := <-adb.udp.SendRecv("MYLISTADD", pm)
147
148                 lid := LID(0)
149
150                 switch reply.Code() {
151                 case 310:
152                         e := adb.parseMylistReply(reply)
153                         if e != nil {
154                                 lid = e.LID
155                         }
156                 case 210:
157                         id, _ := strconv.ParseInt(reply.Lines()[1], 10, 64)
158                         lid = LID(id)
159
160                         // the 310 case does this in parseMylistReply
161                         set.update(user.UID, f, lid)
162                 }
163
164                 intentMap.NotifyClose(lid, key...)
165         }()
166
167         return ch
168 }
169
170 func (adb *AniDB) MyListAddByEd2kSize(ed2k string, size int64, set *MyListSet) <-chan LID {
171         ch := make(chan LID, 1)
172         if size < 1 || !validEd2kHash.MatchString(ed2k) {
173                 ch <- 0
174                 close(ch)
175                 return ch
176         }
177
178         go func() {
179                 ch <- <-adb.MyListAdd(<-adb.FileByEd2kSize(ed2k, size), set)
180                 close(ch)
181         }()
182         return ch
183 }
184
185 func (adb *AniDB) MyListEdit(f *File, set *MyListSet) <-chan bool {
186         ch := make(chan bool, 1)
187         if f == nil {
188                 ch <- false
189                 close(ch)
190                 return ch
191         }
192
193         go func() {
194                 user := <-adb.GetCurrentUser()
195                 if user == nil || user.UID < 1 {
196                         ch <- false
197                         close(ch)
198                         return
199                 }
200
201                 // for the intent map; doesn't get cached
202                 key := []fscache.CacheKey{"mylist-edit", user.UID, f.FID}
203
204                 ic := make(chan notification, 1)
205                 go func() { ch <- (<-ic).(bool); close(ch) }()
206                 if intentMap.Intent(ic, key...) {
207                         return
208                 }
209
210                 pm := set.toParamMap()
211                 pm["edit"] = 1
212                 if lid := f.LID[user.UID]; lid > 0 {
213                         pm["lid"] = lid
214                 } else {
215                         pm["fid"] = f.FID
216                 }
217
218                 reply := <-adb.udp.SendRecv("MYLISTADD", pm)
219
220                 switch reply.Code() {
221                 case 311:
222                         intentMap.NotifyClose(true, key...)
223
224                         set.update(user.UID, f, 0)
225                 default:
226                         intentMap.NotifyClose(false, key...)
227                 }
228         }()
229
230         return ch
231 }
232
233 func (adb *AniDB) MyListDel(f *File) <-chan bool {
234         ch := make(chan bool)
235         if f == nil {
236                 ch <- false
237                 close(ch)
238                 return ch
239         }
240
241         go func() {
242                 user := <-adb.GetCurrentUser()
243                 if user == nil || user.UID < 1 {
244                         ch <- false
245                         close(ch)
246                         return
247                 }
248
249                 // for the intent map; doesn't get cached
250                 key := []fscache.CacheKey{"mylist-del", user.UID, f.FID}
251
252                 ic := make(chan notification, 1)
253                 go func() { ch <- (<-ic).(bool); close(ch) }()
254                 if intentMap.Intent(ic, key...) {
255                         return
256                 }
257
258                 pm := paramMap{}
259                 if lid := f.LID[user.UID]; lid > 0 {
260                         pm["lid"] = lid
261                 } else {
262                         pm["fid"] = f.FID
263                 }
264
265                 reply := <-adb.udp.SendRecv("MYLISTDEL", pm)
266
267                 switch reply.Code() {
268                 case 211:
269                         delete(f.LID, user.UID)
270                         Cache.Set(f, "fid", f.FID)
271                         Cache.Chtime(f.Cached, "fid", f.FID)
272
273                         intentMap.NotifyClose(true, key...)
274                 default:
275                         intentMap.NotifyClose(false, key...)
276                 }
277         }()
278
279         return ch
280 }