github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }