From fd8fde19d990e4d6dbf65eafd4059f4682f311f1 Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Thu, 25 Jul 2013 19:22:59 -0300 Subject: [PATCH] anidb: Support querying for mylist of an Anime It's not very informative due to AniDB-side restrictions. --- misc.go | 5 ++ mylistanime.go | 50 ++++++++++++ mylistanimecache.go | 185 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 mylistanime.go create mode 100644 mylistanimecache.go diff --git a/misc.go b/misc.go index 8393a0f..b74f1c9 100644 --- a/misc.go +++ b/misc.go @@ -15,6 +15,11 @@ var ( GroupCacheDuration = 4 * DefaultCacheDuration // They don't change that often. FileCacheDuration = 8 * DefaultCacheDuration // These change even less often. + MyListCacheDuration = 12 * time.Hour // When the file isn't watched + MyListWatchedCacheDuration = 2 * DefaultCacheDuration // When the file is watched + + LIDCacheDuration = 4 * DefaultCacheDuration + UIDCacheDuration = 16 * DefaultCacheDuration // Can these even be changed? // Used for anime that have already finished airing. diff --git a/mylistanime.go b/mylistanime.go new file mode 100644 index 0000000..3dc407b --- /dev/null +++ b/mylistanime.go @@ -0,0 +1,50 @@ +package anidb + +import ( + "encoding/json" + "github.com/Kovensky/go-anidb/misc" + "strconv" + "time" +) + +type MyListAnime struct { + AID AID + + UnknownState misc.EpisodeList + OnHDD misc.EpisodeList + OnCD misc.EpisodeList + Deleted misc.EpisodeList + + WatchedEpisodes misc.EpisodeList + + EpisodesPerGroup GroupEpisodes + + Cached time.Time +} + +type GroupEpisodes map[GID]misc.EpisodeList + +func (ge GroupEpisodes) MarshalJSON() ([]byte, error) { + generic := make(map[string]misc.EpisodeList, len(ge)) + for k, v := range ge { + generic[strconv.Itoa(int(k))] = v + } + return json.Marshal(generic) +} + +func (ge GroupEpisodes) UnmarshalJSON(b []byte) error { + var generic map[string]misc.EpisodeList + if err := json.Unmarshal(b, &generic); err != nil { + return err + } + for k, v := range generic { + ik, err := strconv.ParseInt(k, 10, 32) + if err != nil { + return err + } + + ge[GID(ik)] = v + } + + return nil +} diff --git a/mylistanimecache.go b/mylistanimecache.go new file mode 100644 index 0000000..c760bf3 --- /dev/null +++ b/mylistanimecache.go @@ -0,0 +1,185 @@ +package anidb + +import ( + "github.com/Kovensky/go-anidb/misc" + "github.com/Kovensky/go-anidb/udp" + "github.com/Kovensky/go-fscache" + "strings" + "time" +) + +func (a *MyListAnime) setCachedTS(ts time.Time) { + a.Cached = ts +} + +func (a *MyListAnime) IsStale() bool { + if a == nil { + return true + } + + return time.Now().Sub(a.Cached) > MyListCacheDuration +} + +var _ cacheable = &MyListAnime{} + +func (uid UID) MyListAnime(aid AID) *MyListAnime { + var a MyListAnime + if CacheGet(&a, "mylist-anime", uid, aid) == nil { + return &a + } + return nil +} + +func (adb *AniDB) MyListAnime(aid AID) <-chan *MyListAnime { + ch := make(chan *MyListAnime, 1) + + if aid < 1 { + ch <- nil + close(ch) + return ch + } + + go func() { + user := <-adb.GetCurrentUser() + if user == nil || user.UID < 1 { + ch <- nil + close(ch) + return + } + key := []fscache.CacheKey{"mylist-anime", user.UID, aid} + + ic := make(chan notification, 1) + go func() { ch <- (<-ic).(*MyListAnime); close(ch) }() + if intentMap.Intent(ic, key...) { + return + } + + if !Cache.IsValid(InvalidKeyCacheDuration, key...) { + intentMap.NotifyClose((*MyListAnime)(nil), key...) + return + } + + entry := user.UID.MyListAnime(aid) + if !entry.IsStale() { + intentMap.NotifyClose(entry, key...) + return + } + + reply := <-adb.udp.SendRecv("MYLIST", paramMap{"aid": aid}) + + switch reply.Code() { + case 221: + r := adb.parseMylistReply(reply) // caches + + // we have only a single file added for this anime -- construct a fake 312 struct + entry = &MyListAnime{AID: aid} + + ep := <-adb.EpisodeByID(r.EID) + list := misc.EpisodeToList(&ep.Episode) + + switch r.MyListState { + case MyListStateUnknown: + entry.UnknownState = list + case MyListStateHDD: + entry.OnHDD = list + case MyListStateCD: + entry.OnCD = list + case MyListStateDeleted: + entry.Deleted = list + } + + if !r.DateWatched.IsZero() { + entry.WatchedEpisodes = list + } + + entry.EpisodesPerGroup = map[GID]misc.EpisodeList{ + r.GID: list, + } + case 312: + entry = adb.parseMylistAnime(reply) + entry.AID = aid + case 321: + Cache.SetInvalid(key...) + } + + CacheSet(entry, key...) + intentMap.NotifyClose(entry, key...) + }() + return ch +} + +func (adb *AniDB) UserMyListAnime(uid UID, aid AID) <-chan *MyListAnime { + key := []fscache.CacheKey{"mylist-anime", uid, aid} + ch := make(chan *MyListAnime, 1) + + if uid < 1 || aid < 1 { + ch <- nil + close(ch) + return ch + } + + ic := make(chan notification, 1) + go func() { ch <- (<-ic).(*MyListAnime); close(ch) }() + if intentMap.Intent(ic, key...) { + return ch + } + + if !Cache.IsValid(InvalidKeyCacheDuration, key...) { + intentMap.NotifyClose((*MyListAnime)(nil), key...) + return ch + } + + entry := uid.MyListAnime(aid) + if !entry.IsStale() { + intentMap.NotifyClose(entry, key...) + return ch + } + + go func() { + user := <-adb.GetCurrentUser() + + if user.UID != uid { // we can't query other users' lists from API + intentMap.NotifyClose(entry, key...) + return + } + + intentMap.NotifyClose(<-adb.MyListAnime(aid), key...) + }() + return ch +} + +func (adb *AniDB) parseMylistAnime(reply udpapi.APIReply) *MyListAnime { + if reply.Code() != 312 { + return nil + } + + parts := strings.Split(reply.Lines()[1], "|") + + // Everything from index 7 on is pairs of group name on odd positions and episode list on even + var groupParts []string + if len(parts) > 7 { + groupParts = parts[7:] + } + + groupMap := make(GroupEpisodes, len(groupParts)/2) + + for i := 0; i+1 < len(groupParts); i += 2 { + g := <-adb.GroupByName(groupParts[i]) + if g == nil { + continue + } + + groupMap[g.GID] = misc.ParseEpisodeList(groupParts[i+1]) + } + + return &MyListAnime{ + UnknownState: misc.ParseEpisodeList(parts[2]), + OnHDD: misc.ParseEpisodeList(parts[3]), + OnCD: misc.ParseEpisodeList(parts[4]), + Deleted: misc.ParseEpisodeList(parts[5]), + + WatchedEpisodes: misc.ParseEpisodeList(parts[6]), + + EpisodesPerGroup: groupMap, + } +} -- 2.44.0