5 "github.com/Kovensky/go-anidb/misc"
6 "github.com/Kovensky/go-anidb/udp"
15 gob.RegisterName("*github.com/Kovensky/go-anidb.File", &File{})
16 gob.RegisterName("*github.com/Kovensky/go-anidb.ed2kCache", &ed2kCache{})
17 gob.RegisterName("github.com/Kovensky/go-anidb.FID", FID(0))
20 func (f *File) Touch() {
24 func (f *File) IsStale() bool {
29 return time.Now().Sub(f.Cached) > FileIncompleteCacheDuration
31 return time.Now().Sub(f.Cached) > FileCacheDuration
38 func (e FID) Touch() {}
39 func (e FID) IsStale() bool { return false }
41 func (fid FID) File() *File {
43 if cache.Get(&f, "fid", fid) == nil {
49 type ed2kCache struct {
54 func (c *ed2kCache) Touch() {
58 func (c *ed2kCache) IsStale() bool {
59 return time.Now().Sub(c.Time) > FileCacheDuration
62 // Prefetches the Anime, Episode and Group that this
63 // file is linked to using the given AniDB instance.
65 // Returns a channel where this file will be sent to
66 // when the prefetching is done; if the file is nil,
67 // the channel will return nil.
68 func (f *File) Prefetch(adb *AniDB) <-chan *File {
69 ch := make(chan *File, 1)
72 a := adb.AnimeByID(f.AID)
73 g := adb.GroupByID(f.GID)
83 // Returns the File from the cache if possible.
85 // If the File is stale, then retrieves the File through the UDP API.
86 func (adb *AniDB) FileByID(fid FID) <-chan *File {
87 keys := []cacheKey{"fid", fid}
89 ch := make(chan *File, 1)
91 ic := make(chan Cacheable, 1)
92 go func() { ch <- (<-ic).(*File); close(ch) }()
93 if intentMap.Intent(ic, keys...) {
97 if !cache.CheckValid(keys...) {
98 intentMap.Notify((*File)(nil), keys...)
102 if f := fid.File(); !f.IsStale() {
103 intentMap.Notify(f, keys...)
108 reply := <-adb.udp.SendRecv("FILE",
116 if reply.Error() == nil {
117 f = parseFileResponse(reply)
118 } else if reply.Code() == 320 {
119 cache.MarkInvalid(keys...)
122 cache.Set(&ed2kCache{FID: f.FID}, "fid", "by-ed2k", f.Ed2kHash, f.Filesize)
123 cache.Set(f, keys...)
125 intentMap.Notify(f, keys...)
130 // Returns the File from the cache if possible.
132 // If the File is stale, then retrieves the File through the UDP API.
133 func (adb *AniDB) FileByEd2kSize(ed2k string, size int64) <-chan *File {
134 keys := []cacheKey{"fid", "by-ed2k", ed2k, size}
136 ch := make(chan *File, 1)
138 ic := make(chan Cacheable, 1)
142 ch <- <-adb.FileByID(fid)
146 if intentMap.Intent(ic, keys...) {
150 if !cache.CheckValid(keys...) {
151 intentMap.Notify(FID(0), keys...)
156 if cache.Get(&ec, keys...) == nil {
157 intentMap.Notify(ec.FID, keys...)
162 reply := <-adb.udp.SendRecv("FILE",
172 if reply.Error() == nil {
173 f = parseFileResponse(reply)
177 cache.Set(&ed2kCache{FID: fid}, keys...)
178 cache.Set(f, "fid", fid)
179 } else if reply.Code() == 320 { // file not found
180 cache.MarkInvalid(keys...)
181 } else if reply.Code() == 322 { // multiple files found
182 panic("Don't know what to do with " + strings.Join(reply.Lines(), "\n"))
185 intentMap.Notify(fid, keys...)
190 var fileFmask = "77da7fe8"
191 var fileAmask = "00008000"
194 fileStateCRCOK = 1 << iota
204 func sanitizeCodec(codec string) string {
208 case "WMV9 (also WMV3)":
218 func parseFileResponse(reply udpapi.APIReply) *File {
219 if reply.Error() != nil {
222 if reply.Truncated() {
226 parts := strings.Split(reply.Lines()[1], "|")
227 ints := make([]int64, len(parts))
228 for i, p := range parts {
229 ints[i], _ = strconv.ParseInt(parts[i], 10, 64)
230 log.Printf("#%d: %s\n", i, p)
233 // how does epno look like?
234 log.Println("epno: " + parts[23])
236 version := FileVersion(1)
237 switch i := ints[6]; {
238 case i&fileStateV5 != 0:
240 case i&fileStateV4 != 0:
242 case i&fileStateV3 != 0:
244 case i&fileStateV2 != 0:
248 // codecs (parts[13]), bitrates (ints[14]), langs (parts[19])
249 codecs := strings.Split(parts[13], "'")
250 bitrates := strings.Split(parts[14], "'")
251 alangs := strings.Split(parts[19], "'")
252 streams := make([]AudioStream, len(codecs))
253 for i := range streams {
254 br, _ := strconv.ParseInt(bitrates[i], 10, 32)
255 streams[i] = AudioStream{
257 Codec: sanitizeCodec(codecs[i]),
258 Language: Language(alangs[i]),
262 sl := strings.Split(parts[20], "'")
263 slangs := make([]Language, len(sl))
265 slangs[i] = Language(sl[i])
268 depth := int(ints[11])
272 res := strings.Split(parts[17], "x")
273 width, _ := strconv.ParseInt(res[0], 10, 32)
274 height, _ := strconv.ParseInt(res[1], 10, 32)
276 Bitrate: int(ints[16]),
277 Codec: sanitizeCodec(parts[15]),
279 Resolution: image.Rect(0, 0, int(width), int(height)),
289 OtherEpisodes: misc.ParseEpisodeList(parts[4]).Simplify(),
290 Deprecated: ints[5] != 0,
292 CRCMatch: ints[6]&fileStateCRCOK != 0,
293 BadCRC: ints[6]&fileStateCRCERR != 0,
295 Uncensored: ints[6]&fileStateUncensored != 0,
296 Censored: ints[6]&fileStateCensored != 0,
298 Incomplete: video.Resolution.Empty(),
305 Source: FileSource(parts[12]),
307 AudioStreams: streams,
308 SubtitleLanguages: slangs,
310 FileExtension: parts[18],
312 Length: time.Duration(ints[21]) * time.Second,
313 AirDate: time.Unix(ints[22], 0),