github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/participantsource.go (about) 1 package chat 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/keybase/client/go/chat/globals" 8 "github.com/keybase/client/go/chat/storage" 9 "github.com/keybase/client/go/chat/types" 10 "github.com/keybase/client/go/chat/utils" 11 "github.com/keybase/client/go/encrypteddb" 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/chat1" 14 "github.com/keybase/client/go/protocol/gregor1" 15 "github.com/keybase/client/go/protocol/keybase1" 16 "golang.org/x/sync/semaphore" 17 ) 18 19 type partDiskStorage struct { 20 Uids []gregor1.UID 21 Hash string 22 } 23 24 type CachingParticipantSource struct { 25 globals.Contextified 26 utils.DebugLabeler 27 28 ri func() chat1.RemoteInterface 29 encryptedDB *encrypteddb.EncryptedDB 30 sema *semaphore.Weighted 31 locktab *libkb.LockTable 32 notify func(interface{}) 33 } 34 35 var _ types.ParticipantSource = (*CachingParticipantSource)(nil) 36 37 func NewCachingParticipantSource(g *globals.Context, ri func() chat1.RemoteInterface) *CachingParticipantSource { 38 keyFn := func(ctx context.Context) ([32]byte, error) { 39 return storage.GetSecretBoxKey(ctx, g.ExternalG()) 40 } 41 dbFn := func(g *libkb.GlobalContext) *libkb.JSONLocalDb { 42 return g.LocalChatDb 43 } 44 notify, notifyCancel := libkb.ThrottleBatch(func(batchedInt interface{}) { 45 batched, _ := batchedInt.(map[chat1.ConvIDStr][]chat1.UIParticipant) 46 g.NotifyRouter.HandleChatParticipantsInfo(context.Background(), batched) 47 }, func(batchedInt, singleInt interface{}) interface{} { 48 batched, _ := batchedInt.(map[chat1.ConvIDStr][]chat1.UIParticipant) 49 single, _ := singleInt.(map[chat1.ConvIDStr][]chat1.UIParticipant) 50 for convIDStr, parts := range single { 51 batched[convIDStr] = parts 52 } 53 return batched 54 }, func() interface{} { 55 return make(map[chat1.ConvIDStr][]chat1.UIParticipant) 56 }, 57 200*time.Millisecond, true) 58 g.PushShutdownHook(func(mctx libkb.MetaContext) error { 59 notifyCancel() 60 return nil 61 }) 62 return &CachingParticipantSource{ 63 Contextified: globals.NewContextified(g), 64 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "CachingParticipantSource", false), 65 locktab: libkb.NewLockTable(), 66 ri: ri, 67 encryptedDB: encrypteddb.New(g.ExternalG(), dbFn, keyFn), 68 sema: semaphore.NewWeighted(20), 69 notify: notify, 70 } 71 } 72 73 func (s *CachingParticipantSource) Get(ctx context.Context, uid gregor1.UID, 74 convID chat1.ConversationID, dataSource types.InboxSourceDataSourceTyp) (res []gregor1.UID, err error) { 75 defer s.Trace(ctx, &err, "Get")() 76 ch := s.GetNonblock(ctx, uid, convID, dataSource) 77 for r := range ch { 78 if r.Err != nil { 79 return res, err 80 } 81 res = r.Uids 82 } 83 return res, nil 84 } 85 86 func (s *CachingParticipantSource) dbKey(uid gregor1.UID, convID chat1.ConversationID) libkb.DbKey { 87 return libkb.DbKey{ 88 Typ: libkb.DBChatParticipants, 89 Key: uid.String() + convID.String(), 90 } 91 } 92 93 func (s *CachingParticipantSource) GetNonblock(ctx context.Context, uid gregor1.UID, 94 convID chat1.ConversationID, dataSource types.InboxSourceDataSourceTyp) (resCh chan types.ParticipantResult) { 95 resCh = make(chan types.ParticipantResult, 1) 96 go func(ctx context.Context) { 97 defer s.Trace(ctx, nil, "GetNonblock")() 98 defer close(resCh) 99 lock := s.locktab.AcquireOnName(ctx, s.G(), convID.String()) 100 defer lock.Release(ctx) 101 102 conv, err := utils.GetUnverifiedConv(ctx, s.G(), uid, convID, dataSource) 103 if err != nil { 104 resCh <- types.ParticipantResult{Err: err} 105 return 106 } 107 108 switch conv.GetMembersType() { 109 case chat1.ConversationMembersType_TEAM: 110 // handle team separately in here 111 default: 112 resCh <- types.ParticipantResult{Uids: conv.Conv.Metadata.AllList} 113 return 114 } 115 116 // load local first and send to channel 117 localHash := "" 118 switch dataSource { 119 case types.InboxSourceDataSourceAll, types.InboxSourceDataSourceLocalOnly: 120 var local partDiskStorage 121 found, err := s.encryptedDB.Get(ctx, s.dbKey(uid, conv.GetConvID()), &local) 122 if err != nil { 123 resCh <- types.ParticipantResult{Err: err} 124 if err := s.encryptedDB.Delete(ctx, s.dbKey(uid, conv.GetConvID())); err != nil { 125 s.Debug(ctx, "GetNonblock: failed to delete after read error: %s", err) 126 } 127 return 128 } 129 if found { 130 resCh <- types.ParticipantResult{Uids: local.Uids} 131 localHash = local.Hash 132 } 133 default: 134 } 135 136 // load remote if necessary 137 switch dataSource { 138 case types.InboxSourceDataSourceAll, types.InboxSourceDataSourceRemoteOnly: 139 partRes, err := s.ri().RefreshParticipantsRemote(ctx, chat1.RefreshParticipantsRemoteArg{ 140 ConvID: conv.GetConvID(), 141 Hash: localHash, 142 }) 143 if err != nil { 144 resCh <- types.ParticipantResult{Err: err} 145 return 146 } 147 if partRes.HashMatch { 148 s.Debug(ctx, "GetNonblock: hash match on remote, all done") 149 return 150 } 151 152 if err := s.encryptedDB.Put(ctx, s.dbKey(uid, conv.GetConvID()), partDiskStorage{ 153 Uids: partRes.Uids, 154 Hash: partRes.Hash, 155 }); err != nil { 156 s.Debug(ctx, "GetNonblock: failed to store participants: %s", err) 157 } 158 resCh <- types.ParticipantResult{Uids: partRes.Uids} 159 default: 160 } 161 }(globals.BackgroundChatCtx(ctx, s.G())) 162 return resCh 163 } 164 165 func (s *CachingParticipantSource) GetWithNotifyNonblock(ctx context.Context, uid gregor1.UID, 166 convID chat1.ConversationID, dataSource types.InboxSourceDataSourceTyp) { 167 go func(ctx context.Context) { 168 _ = s.sema.Acquire(ctx, 1) 169 defer s.sema.Release(1) 170 171 convIDStr := convID.ConvIDStr() 172 ch := s.G().ParticipantsSource.GetNonblock(ctx, uid, convID, dataSource) 173 for r := range ch { 174 participants, err := s.GetParticipantsFromUids(ctx, r.Uids) 175 if err != nil { 176 s.Debug(ctx, "GetWithNotifyNonblock: failed to map uids: %s", err) 177 continue 178 } 179 s.notify(map[chat1.ConvIDStr][]chat1.UIParticipant{ 180 convIDStr: utils.PresentConversationParticipantsLocal(ctx, participants), 181 }) 182 } 183 }(globals.BackgroundChatCtx(ctx, s.G())) 184 } 185 186 func (s *CachingParticipantSource) GetParticipantsFromUids( 187 ctx context.Context, 188 uids []gregor1.UID, 189 ) (participants []chat1.ConversationLocalParticipant, err error) { 190 kuids := make([]keybase1.UID, 0, len(uids)) 191 for _, uid := range uids { 192 kuids = append(kuids, keybase1.UID(uid.String())) 193 } 194 rows, err := s.G().UIDMapper.MapUIDsToUsernamePackages(ctx, s.G(), kuids, time.Hour*24, 195 time.Minute, true) 196 if err != nil { 197 return nil, err 198 } 199 participants = make([]chat1.ConversationLocalParticipant, 0, len(uids)) 200 for _, row := range rows { 201 participants = append(participants, utils.UsernamePackageToParticipant(row)) 202 } 203 return participants, nil 204 }