package anidb import ( "github.com/EliasFleckenstein03/go-anidb/udp" "github.com/EliasFleckenstein03/go-fscache" "strconv" "strings" "sync" "time" ) type UID int func (uid UID) User() *User { var u User if CacheGet(&u, "user", uid) == nil { return &u } return nil } func UserByName(name string) *User { var uid UID if CacheGet(&uid, "user", "by-name", name) == nil { return uid.User() } return nil } func (adb *AniDB) GetCurrentUser() <-chan *User { ch := make(chan *User, 1) if adb.udp.credentials == nil { ch <- nil close(ch) return ch } return adb.GetUserByName(decrypt(adb.udp.credentials.username)) } // This is an (almost) entirely local representation. func (adb *AniDB) GetUserByID(uid UID) <-chan *User { key := []fscache.CacheKey{"user", uid} ch := make(chan *User, 1) if uid < 1 { ch <- nil close(ch) return ch } ic := make(chan notification, 1) go func() { ch <- (<-ic).(*User); close(ch) }() if intentMap.Intent(ic, key...) { return ch } if !Cache.IsValid(InvalidKeyCacheDuration, key...) { intentMap.NotifyClose((*User)(nil), key...) return ch } go func() { var user *User if CacheGet(&user, key...) == nil { intentMap.NotifyClose(user, key...) return } <-adb.GetUserName(uid) CacheGet(&user, key...) intentMap.NotifyClose(user) }() return ch } func (adb *AniDB) GetUserByName(username string) <-chan *User { ch := make(chan *User, 1) if username == "" { ch <- nil close(ch) return ch } go func() { ch <- <-adb.GetUserByID(<-adb.GetUserUID(username)) close(ch) }() return ch } func (adb *AniDB) GetUserUID(username string) <-chan UID { key := []fscache.CacheKey{"user", "by-name", username} ch := make(chan UID, 1) if username == "" { ch <- 0 close(ch) return ch } ic := make(chan notification, 1) go func() { ch <- (<-ic).(UID); close(ch) }() if intentMap.Intent(ic, key...) { return ch } if !Cache.IsValid(InvalidKeyCacheDuration, key...) { intentMap.NotifyClose((UID)(0), key...) return ch } uid := UID(0) switch ts, err := Cache.Get(&uid, key...); { case err == nil && time.Now().Sub(ts) < UIDCacheDuration: intentMap.NotifyClose(uid, key...) return ch } go func() { reply := <-adb.udp.SendRecv("USER", paramMap{"user": username}) switch reply.Code() { case 295: uid, _ = parseUserReply(reply) // caches case 394: Cache.SetInvalid(key...) } intentMap.NotifyClose(uid, key...) }() return ch } func (adb *AniDB) GetUserName(uid UID) <-chan string { key := []fscache.CacheKey{"user", "by-uid", uid} ch := make(chan string, 1) if uid < 1 { ch <- "" close(ch) return ch } ic := make(chan notification, 1) go func() { ch <- (<-ic).(string); close(ch) }() if intentMap.Intent(ic, key...) { return ch } if !Cache.IsValid(InvalidKeyCacheDuration, key...) { intentMap.NotifyClose("", key...) return ch } name := "" switch ts, err := Cache.Get(&name, key...); { case err == nil && time.Now().Sub(ts) < UIDCacheDuration: intentMap.NotifyClose(name, key...) return ch } go func() { reply := <-adb.udp.SendRecv("USER", paramMap{"uid": uid}) switch reply.Code() { case 295: _, name = parseUserReply(reply) // caches case 394: Cache.SetInvalid(key...) } intentMap.NotifyClose(name, key...) }() return ch } var userReplyMutex sync.Mutex func parseUserReply(reply udpapi.APIReply) (UID, string) { userReplyMutex.Lock() defer userReplyMutex.Unlock() if reply.Error() == nil { parts := strings.Split(reply.Lines()[1], "|") id, _ := strconv.ParseInt(parts[0], 10, 32) CacheSet(UID(id), "user", "by-name", parts[1]) CacheSet(parts[1], "user", "by-uid", id) if _, err := Cache.Stat("user", id); err != nil { CacheSet(&User{ UID: UID(id), Username: parts[1], }, "user", id) } return UID(id), parts[1] } return 0, "" }