6 "github.com/Kovensky/go-anidb/misc"
7 "github.com/Kovensky/go-anidb/udp"
17 gob.RegisterName("*github.com/Kovensky/go-anidb.File", &File{})
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
36 func (fid FID) File() *File {
37 f, _ := caches.Get(fileCache).Get(int(fid)).(*File)
41 func ed2kKey(ed2k string, size int64) string {
42 return fmt.Sprintf("%s-%016x", ed2k, size)
45 func ed2kCache(f *File) {
48 defer ed2kFidLock.Unlock()
49 ed2kFidMap[ed2kKey(f.Ed2kHash, f.Filesize)] = f.FID
53 // Prefetches the Anime, Episode and Group that this
54 // file is linked to using the given AniDB instance.
56 // Returns a channel where this file will be sent to
57 // when the prefetching is done; if the file is nil,
58 // the channel will return nil.
59 func (f *File) Prefetch(adb *AniDB) <-chan *File {
60 ch := make(chan *File, 1)
63 a := adb.AnimeByID(f.AID)
64 g := adb.GroupByID(f.GID)
74 var ed2kFidMap = map[string]FID{}
75 var ed2kIntent = map[string][]chan *File{}
76 var ed2kFidLock = sync.RWMutex{}
78 func (adb *AniDB) FileByID(fid FID) <-chan *File {
79 ch := make(chan *File, 1)
80 if f := fid.File(); !f.IsStale() {
86 fc := caches.Get(fileCache)
87 ic := make(chan Cacheable, 1)
88 go func() { ch <- (<-ic).(*File); close(ch) }()
89 if fc.Intent(int(fid), ic) {
94 reply := <-adb.udp.SendRecv("FILE",
102 if reply.Error() == nil {
103 f = parseFileResponse(reply)
111 func (adb *AniDB) FileByEd2kSize(ed2k string, size int64) <-chan *File {
112 key := ed2kKey(ed2k, size)
113 ch := make(chan *File, 1)
116 if fid, ok := ed2kFidMap[key]; ok {
117 ed2kFidLock.RUnlock()
118 if f := fid.File(); f != nil {
123 return adb.FileByID(fid)
125 ed2kFidLock.RUnlock()
128 if list, ok := ed2kIntent[key]; ok {
129 ed2kIntent[key] = append(list, ch)
132 ed2kIntent[key] = append(list, ch)
136 reply := <-adb.udp.SendRecv("FILE",
145 if reply.Error() == nil {
146 f = parseFileResponse(reply)
149 caches.Get(fileCache).Set(int(f.FID), f)
150 } else if reply.Code() == 320 { // file not found
152 delete(ed2kFidMap, key)
154 } else if reply.Code() == 322 { // multiple files found
155 panic("Don't know what to do with " + strings.Join(reply.Lines(), "\n"))
159 defer ed2kFidLock.Unlock()
161 for _, ch := range ed2kIntent[key] {
165 delete(ed2kIntent, key)
170 var fileFmask = "77da7fe8"
171 var fileAmask = "00008000"
174 fileStateCRCOK = 1 << iota
184 func sanitizeCodec(codec string) string {
188 case "WMV9 (also WMV3)":
198 func parseFileResponse(reply udpapi.APIReply) *File {
199 if reply.Error() != nil {
202 if reply.Truncated() {
206 parts := strings.Split(reply.Lines()[1], "|")
207 ints := make([]int64, len(parts))
208 for i, p := range parts {
209 ints[i], _ = strconv.ParseInt(parts[i], 10, 64)
210 log.Printf("#%d: %s\n", i, p)
213 // how does epno look like?
214 log.Println("epno: " + parts[23])
216 version := FileVersion(1)
217 switch i := ints[6]; {
218 case i&fileStateV5 != 0:
220 case i&fileStateV4 != 0:
222 case i&fileStateV3 != 0:
224 case i&fileStateV2 != 0:
228 // codecs (parts[13]), bitrates (ints[14]), langs (parts[19])
229 codecs := strings.Split(parts[13], "'")
230 bitrates := strings.Split(parts[14], "'")
231 alangs := strings.Split(parts[19], "'")
232 streams := make([]AudioStream, len(codecs))
233 for i := range streams {
234 br, _ := strconv.ParseInt(bitrates[i], 10, 32)
235 streams[i] = AudioStream{
237 Codec: sanitizeCodec(codecs[i]),
238 Language: Language(alangs[i]),
242 sl := strings.Split(parts[20], "'")
243 slangs := make([]Language, len(sl))
245 slangs[i] = Language(sl[i])
248 depth := int(ints[11])
252 res := strings.Split(parts[17], "x")
253 width, _ := strconv.ParseInt(res[0], 10, 32)
254 height, _ := strconv.ParseInt(res[1], 10, 32)
256 Bitrate: int(ints[16]),
257 Codec: sanitizeCodec(parts[15]),
259 Resolution: image.Rect(0, 0, int(width), int(height)),
269 OtherEpisodes: misc.ParseEpisodeList(parts[4]).Simplify(),
270 Deprecated: ints[5] != 0,
272 CRCMatch: ints[6]&fileStateCRCOK != 0,
273 BadCRC: ints[6]&fileStateCRCERR != 0,
275 Uncensored: ints[6]&fileStateUncensored != 0,
276 Censored: ints[6]&fileStateCensored != 0,
278 Incomplete: video.Resolution.Empty(),
285 Source: FileSource(parts[12]),
287 AudioStreams: streams,
288 SubtitleLanguages: slangs,
290 FileExtension: parts[18],
292 Length: time.Duration(ints[21]) * time.Second,
293 AirDate: time.Unix(ints[22], 0),