From e4b7ed4c1d7138ca1f38ef4f903d08e552887a6a Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Wed, 17 Jul 2013 18:04:59 -0300 Subject: [PATCH] anidb: Implement MYLIST for single files --- mylist.go | 52 ++++++++++++ mylistcache.go | 217 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 mylistcache.go diff --git a/mylist.go b/mylist.go index 8f7288c..de3e187 100644 --- a/mylist.go +++ b/mylist.go @@ -1,3 +1,55 @@ package anidb +import ( + "time" +) + type LID int64 + +type MyListState int + +const ( + MyListStateUnknown = MyListState(iota) + MyListStateHDD + MyListStateCD + MyListStateDeleted +) + +type FileState int + +const ( + FileStateOriginal = FileState(iota) + FileStateCorrupted + FileStateEdited + + FileStateOther = 100 +) +const ( + FileStateSelfRip = FileState(10 + iota) + FileStateDVD + FileStateVHS + FileStateTV + FileStateTheaters + FileStateStreamed +) + +type MyListEntry struct { + LID LID + + FID FID + EID EID + AID AID + GID GID + + DateAdded time.Time + DateWatched time.Time + + State FileState + MyListState MyListState + + Storage string + Source string + Other string + + Cached time.Time +} diff --git a/mylistcache.go b/mylistcache.go new file mode 100644 index 0000000..d7ad4eb --- /dev/null +++ b/mylistcache.go @@ -0,0 +1,217 @@ +package anidb + +import ( + "github.com/Kovensky/go-anidb/udp" + "github.com/Kovensky/go-fscache" + "strconv" + "strings" + "time" +) + +func (e *MyListEntry) setCachedTS(ts time.Time) { + e.Cached = ts +} + +func (e *MyListEntry) IsStale() bool { + if e == nil { + return true + } + + max := MyListCacheDuration + if !e.DateWatched.IsZero() { + max = MyListWatchedCacheDuration + } + return time.Now().Sub(e.Cached) > max +} + +var _ cacheable = &MyListEntry{} + +func (lid LID) MyListEntry() *MyListEntry { + var e MyListEntry + if CacheGet(&e, "lid", lid) == nil { + return &e + } + return nil +} + +func (adb *AniDB) MyListByFile(f *File) <-chan *MyListEntry { + ch := make(chan *MyListEntry, 1) + + if f == nil { + ch <- nil + close(ch) + return ch + } + + go func() { + user := <-adb.GetCurrentUser() + + var entry *MyListEntry + + if lid := f.LID[user.UID]; lid != 0 { + entry = <-adb.MyListByLID(lid) + } + if entry == nil { + entry = <-adb.MyListByFID(f.FID) + } + ch <- entry + close(ch) + }() + return ch +} + +func (adb *AniDB) MyListByLID(lid LID) <-chan *MyListEntry { + key := []fscache.CacheKey{"mylist", lid} + ch := make(chan *MyListEntry, 1) + + if lid < 1 { + ch <- nil + close(ch) + return ch + } + + ic := make(chan notification, 1) + go func() { ch <- (<-ic).(*MyListEntry); close(ch) }() + if intentMap.Intent(ic, key...) { + return ch + } + + if !Cache.IsValid(InvalidKeyCacheDuration, key...) { + intentMap.NotifyClose((*MyListEntry)(nil), key...) + return ch + } + + entry := lid.MyListEntry() + if !entry.IsStale() { + intentMap.NotifyClose(entry, key...) + return ch + } + + go func() { + reply := <-adb.udp.SendRecv("MYLIST", paramMap{"lid": lid}) + + switch reply.Code() { + case 221: + entry = adb.parseMylistReply(reply) // caches + case 312: + panic("Multiple MYLIST entries when querying for single LID") + case 321: + Cache.SetInvalid(key...) + } + + intentMap.NotifyClose(entry, key...) + }() + return ch +} + +func (adb *AniDB) MyListByFID(fid FID) <-chan *MyListEntry { + ch := make(chan *MyListEntry, 1) + + if fid < 1 { + ch <- nil + close(ch) + return ch + } + + // This is an odd one: we lack enough data at first to create the cache key + go func() { + user := <-adb.GetCurrentUser() + if user == nil || user.UID < 1 { + ch <- nil + close(ch) + return + } + + key := []fscache.CacheKey{"mylist", "by-fid", fid, user.UID} + + ic := make(chan notification, 1) + go func() { ch <- (<-ic).(*MyListEntry); close(ch) }() + if intentMap.Intent(ic, key...) { + return + } + + if !Cache.IsValid(InvalidKeyCacheDuration, key...) { + intentMap.NotifyClose((*MyListEntry)(nil), key...) + return + } + + lid := LID(0) + switch ts, err := Cache.Get(&lid, key...); { + case err == nil && time.Now().Sub(ts) < LIDCacheDuration: + intentMap.NotifyClose(<-adb.MyListByLID(lid), key...) + return + } + + reply := <-adb.udp.SendRecv("MYLIST", paramMap{"fid": fid}) + + var entry *MyListEntry + + switch reply.Code() { + case 221: + entry = adb.parseMylistReply(reply) // caches + case 312: + panic("Multiple MYLIST entries when querying for single FID") + case 321: + Cache.SetInvalid(key...) + } + + intentMap.NotifyClose(entry, key...) + }() + return ch +} + +func (adb *AniDB) parseMylistReply(reply udpapi.APIReply) *MyListEntry { + if reply.Error() != nil { + return nil + } + + parts := strings.Split(reply.Lines()[1], "|") + ints := make([]int64, len(parts)) + for i := range parts { + ints[i], _ = strconv.ParseInt(parts[i], 10, 64) + } + + da := time.Unix(ints[5], 0) + if ints[5] == 0 { + da = time.Time{} + } + dw := time.Unix(ints[7], 0) + if ints[7] == 0 { + dw = time.Time{} + } + + e := &MyListEntry{ + LID: LID(ints[0]), + + FID: FID(ints[1]), + EID: EID(ints[2]), + AID: AID(ints[3]), + GID: GID(ints[4]), + + DateAdded: da, + DateWatched: dw, + + State: FileState(ints[11]), + MyListState: MyListState(ints[6]), + + Storage: parts[8], + Source: parts[9], + Other: parts[10], + } + + user := <-adb.GetCurrentUser() + + if user != nil { + if f := e.FID.File(); f != nil { + f.LID[user.UID] = e.LID + cacheFile(f) + } + + CacheSet(e, "mylist", "by-fid", e.FID, user.UID) + } + + // TODO: Add mylist info to Anime, also update there + CacheSet(e, "mylist", e.LID) + + return e +} -- 2.44.0