github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/sync_trackers.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "fmt" 8 "sync" 9 "time" 10 11 keybase1 "github.com/keybase/client/go/protocol/keybase1" 12 ) 13 14 type FollowDirection int 15 16 const ( 17 FollowDirectionFollowing FollowDirection = 0 18 FollowDirectionFollowers FollowDirection = 1 19 ) 20 21 func directionToReverse(direction FollowDirection) (reverse bool) { 22 return direction == FollowDirectionFollowing 23 } 24 25 type ServertrustTrackerSyncer struct { 26 sync.Mutex 27 Contextified 28 res *keybase1.UserSummarySet 29 direction FollowDirection 30 dirty bool 31 callerUID keybase1.UID 32 } 33 34 const cacheTimeout = 10 * time.Minute 35 36 func (t *ServertrustTrackerSyncer) dbKey(u keybase1.UID) DbKey { 37 if t.direction == FollowDirectionFollowing { 38 return DbKeyUID(DBUnverifiedTrackersFollowing, u) 39 } 40 return DbKeyUID(DBUnverifiedTrackersFollowers, u) 41 } 42 43 func (t *ServertrustTrackerSyncer) loadFromStorage(m MetaContext, uid keybase1.UID, useExpiration bool) error { 44 var err error 45 var found bool 46 var tmp keybase1.UserSummarySet 47 defer m.Trace(fmt.Sprintf("loadFromStorage(%s)", uid), &err)() 48 found, err = t.G().LocalDb.GetInto(&tmp, t.dbKey(uid)) 49 if err != nil { 50 return err 51 } 52 if !found { 53 m.Debug("| no cached copy found") 54 return nil 55 } 56 cachedAt := keybase1.FromTime(tmp.Time) 57 if useExpiration && time.Since(cachedAt) > cacheTimeout { 58 m.Debug("| expired; cached at %s", cachedAt) 59 return nil 60 } 61 m.Debug("| found a record, cached %s", cachedAt) 62 t.res = &tmp 63 return nil 64 } 65 66 func (t *ServertrustTrackerSyncer) getLoadedVersion() int { 67 ret := -1 68 if t.res != nil { 69 ret = t.res.Version 70 } 71 return ret 72 } 73 74 func (t *ServertrustTrackerSyncer) syncFromServer(m MetaContext, uid keybase1.UID, forceReload bool) (err error) { 75 76 defer m.Trace(fmt.Sprintf("syncFromServer(%s)", uid), &err)() 77 78 hargs := HTTPArgs{ 79 "uid": UIDArg(uid), 80 "reverse": B{directionToReverse(t.direction)}, 81 "autoCamel": B{true}, 82 "caller_uid": UIDArg(t.callerUID), 83 } 84 lv := t.getLoadedVersion() 85 if lv >= 0 && !forceReload { 86 hargs.Add("version", I{lv}) 87 } 88 var res *APIRes 89 res, err = m.G().API.Get(m, APIArg{ 90 Endpoint: "user/list_followers_for_display", 91 Args: hargs, 92 }) 93 m.Debug("| syncFromServer() -> %s", ErrToOk(err)) 94 if err != nil { 95 return err 96 } 97 var tmp keybase1.UserSummarySet 98 if err = res.Body.UnmarshalAgain(&tmp); err != nil { 99 return 100 } 101 tmp.Time = keybase1.ToTime(time.Now()) 102 if lv < 0 || tmp.Version > lv || forceReload { 103 m.Debug("| syncFromServer(): got update %d > %d (%d records)", tmp.Version, lv, 104 len(tmp.Users)) 105 t.res = &tmp 106 t.dirty = true 107 } else { 108 m.Debug("| syncFromServer(): no change needed @ %d", lv) 109 } 110 return nil 111 } 112 113 func (t *ServertrustTrackerSyncer) store(m MetaContext, uid keybase1.UID) error { 114 var err error 115 if !t.dirty { 116 return err 117 } 118 119 if err = t.G().LocalDb.PutObj(t.dbKey(uid), nil, t.res); err != nil { 120 return err 121 } 122 123 t.dirty = false 124 return nil 125 } 126 127 func (t *ServertrustTrackerSyncer) needsLogin(m MetaContext) bool { 128 return false 129 } 130 131 func (t *ServertrustTrackerSyncer) Block(m MetaContext, badUIDs map[keybase1.UID]bool) (err error) { 132 defer m.Trace(fmt.Sprintf("ServertrustTrackerSyncer#Block(%+v)", badUIDs), &err)() 133 t.Lock() 134 defer t.Unlock() 135 136 if t.direction != FollowDirectionFollowers { 137 return fmt.Errorf("can only delete users out of followers cache") 138 } 139 140 if t.res == nil { 141 m.Debug("No followers loaded, so nothing to do") 142 return nil 143 } 144 145 err = t.loadFromStorage(m, t.callerUID, true) 146 if err != nil { 147 return err 148 } 149 150 var newUsers []keybase1.UserSummary 151 for _, userSummary := range t.res.Users { 152 if badUIDs[userSummary.Uid] { 153 m.Debug("Filtering bad user out of state: %s", userSummary.Uid) 154 t.dirty = true 155 } else { 156 newUsers = append(newUsers, userSummary) 157 } 158 } 159 t.res.Users = newUsers 160 err = t.store(m, t.callerUID) 161 return err 162 } 163 164 func (t *ServertrustTrackerSyncer) Result() keybase1.UserSummarySet { 165 if t.res == nil { 166 return keybase1.UserSummarySet{} 167 } 168 169 // Normalize usernames 170 var normalizedUsers []keybase1.UserSummary 171 for _, u := range t.res.Users { 172 normalizedUser := u 173 normalizedUser.Username = NewNormalizedUsername(u.Username).String() 174 normalizedUsers = append(normalizedUsers, normalizedUser) 175 } 176 t.res.Users = normalizedUsers 177 178 return *t.res 179 } 180 181 func NewServertrustTrackerSyncer(g *GlobalContext, callerUID keybase1.UID, direction FollowDirection) *ServertrustTrackerSyncer { 182 return &ServertrustTrackerSyncer{ 183 Contextified: NewContextified(g), 184 direction: direction, 185 callerUID: callerUID, 186 } 187 }