5 "github.com/Kovensky/go-anidb/misc"
6 "github.com/Kovensky/go-anidb/udp"
16 gob.RegisterName("*github.com/Kovensky/go-anidb.File", &File{})
17 gob.RegisterName("*github.com/Kovensky/go-anidb.fidCache", &fidCache{})
18 gob.RegisterName("github.com/Kovensky/go-anidb.FID", FID(0))
21 func (f *File) Touch() {
25 func (f *File) IsStale() bool {
30 return time.Now().Sub(f.Cached) > FileIncompleteCacheDuration
32 return time.Now().Sub(f.Cached) > FileCacheDuration
39 func (e FID) Touch() {}
40 func (e FID) IsStale() bool { return false }
42 func (fid FID) File() *File {
44 if cache.Get(&f, "fid", fid) == nil {
50 type fidCache struct {
55 func (c *fidCache) Touch() {
59 func (c *fidCache) IsStale() bool {
60 return time.Now().Sub(c.Time) > FileCacheDuration
63 // Prefetches the Anime, Episode and Group that this
64 // file is linked to using the given AniDB instance.
66 // Returns a channel where this file will be sent to
67 // when the prefetching is done; if the file is nil,
68 // the channel will return nil.
69 func (f *File) Prefetch(adb *AniDB) <-chan *File {
70 ch := make(chan *File, 1)
73 a := adb.AnimeByID(f.AID)
74 g := adb.GroupByID(f.GID)
84 // Retrieves a File by its FID. Uses the UDP API.
85 func (adb *AniDB) FileByID(fid FID) <-chan *File {
86 keys := []cacheKey{"fid", fid}
88 ch := make(chan *File, 1)
96 ic := make(chan Cacheable, 1)
97 go func() { ch <- (<-ic).(*File); close(ch) }()
98 if intentMap.Intent(ic, keys...) {
102 if !cache.CheckValid(keys...) {
103 intentMap.Notify((*File)(nil), keys...)
109 intentMap.Notify(f, keys...)
114 reply := <-adb.udp.SendRecv("FILE",
121 if reply.Error() == nil {
122 f = parseFileResponse(reply)
124 cache.Set(&fidCache{FID: f.FID}, "fid", "by-ed2k", f.Ed2kHash, f.Filesize)
125 cache.Set(f, keys...)
126 } else if reply.Code() == 320 {
127 cache.MarkInvalid(keys...)
130 intentMap.Notify(f, keys...)
135 var validEd2kHash = regexp.MustCompile(`\A[:xdigit:]{32}\z`)
137 // Retrieves a File by its Ed2kHash + Filesize combination. Uses the UDP API.
138 func (adb *AniDB) FileByEd2kSize(ed2k string, size int64) <-chan *File {
139 keys := []cacheKey{"fid", "by-ed2k", ed2k, size}
141 ch := make(chan *File, 1)
143 if size < 1 || !validEd2kHash.MatchString(ed2k) {
148 // AniDB always uses lower case hashes
149 ed2k = strings.ToLower(ed2k)
151 ic := make(chan Cacheable, 1)
155 ch <- <-adb.FileByID(fid)
159 if intentMap.Intent(ic, keys...) {
163 if !cache.CheckValid(keys...) {
164 intentMap.Notify(FID(0), keys...)
171 if cache.Get(&ec, keys...) == nil && !ec.IsStale() {
172 intentMap.Notify(ec.FID, keys...)
178 reply := <-adb.udp.SendRecv("FILE",
187 if reply.Error() == nil {
188 f = parseFileResponse(reply)
192 cache.Set(&fidCache{FID: fid}, keys...)
193 cache.Set(f, "fid", fid)
194 } else if reply.Code() == 320 { // file not found
195 cache.MarkInvalid(keys...)
196 } else if reply.Code() == 322 { // multiple files found
197 panic("Don't know what to do with " + strings.Join(reply.Lines(), "\n"))
200 intentMap.Notify(fid, keys...)
205 var fileFmask = "77da7fe8"
206 var fileAmask = "00008000"
209 fileStateCRCOK = 1 << iota
219 func sanitizeCodec(codec string) string {
223 case "WMV9 (also WMV3)":
233 func parseFileResponse(reply udpapi.APIReply) *File {
234 if reply.Error() != nil {
237 if reply.Truncated() {
241 parts := strings.Split(reply.Lines()[1], "|")
242 ints := make([]int64, len(parts))
243 for i, p := range parts {
244 ints[i], _ = strconv.ParseInt(parts[i], 10, 64)
245 log.Printf("#%d: %s\n", i, p)
248 // how does epno look like?
249 log.Println("epno: " + parts[23])
251 version := FileVersion(1)
252 switch i := ints[6]; {
253 case i&fileStateV5 != 0:
255 case i&fileStateV4 != 0:
257 case i&fileStateV3 != 0:
259 case i&fileStateV2 != 0:
263 // codecs (parts[13]), bitrates (ints[14]), langs (parts[19])
264 codecs := strings.Split(parts[13], "'")
265 bitrates := strings.Split(parts[14], "'")
266 alangs := strings.Split(parts[19], "'")
267 streams := make([]AudioStream, len(codecs))
268 for i := range streams {
269 br, _ := strconv.ParseInt(bitrates[i], 10, 32)
270 streams[i] = AudioStream{
272 Codec: sanitizeCodec(codecs[i]),
273 Language: Language(alangs[i]),
277 sl := strings.Split(parts[20], "'")
278 slangs := make([]Language, len(sl))
280 slangs[i] = Language(sl[i])
283 depth := int(ints[11])
287 res := strings.Split(parts[17], "x")
288 width, _ := strconv.ParseInt(res[0], 10, 32)
289 height, _ := strconv.ParseInt(res[1], 10, 32)
291 Bitrate: int(ints[16]),
292 Codec: sanitizeCodec(parts[15]),
294 Resolution: image.Rect(0, 0, int(width), int(height)),
304 OtherEpisodes: misc.ParseEpisodeList(parts[4]).Simplify(),
305 Deprecated: ints[5] != 0,
307 CRCMatch: ints[6]&fileStateCRCOK != 0,
308 BadCRC: ints[6]&fileStateCRCERR != 0,
310 Uncensored: ints[6]&fileStateUncensored != 0,
311 Censored: ints[6]&fileStateCensored != 0,
313 Incomplete: video.Resolution.Empty(),
320 Source: FileSource(parts[12]),
322 AudioStreams: streams,
323 SubtitleLanguages: slangs,
325 FileExtension: parts[18],
327 Length: time.Duration(ints[21]) * time.Second,
328 AirDate: time.Unix(ints[22], 0),