github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/teamchannelsource.go (about)

     1  package chat
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/keybase/client/go/chat/globals"
    11  	"github.com/keybase/client/go/chat/types"
    12  	"github.com/keybase/client/go/chat/utils"
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/chat1"
    15  	"github.com/keybase/client/go/protocol/gregor1"
    16  	"github.com/keybase/client/go/protocol/keybase1"
    17  )
    18  
    19  type recentJoinsCacheItem struct {
    20  	numJoins int
    21  	mtime    gregor1.Time
    22  }
    23  
    24  type recentJoinsMemCache struct {
    25  	sync.RWMutex
    26  	cache map[chat1.ConvIDStr]recentJoinsCacheItem
    27  }
    28  
    29  func newRecentJoinsMemCache() *recentJoinsMemCache {
    30  	return &recentJoinsMemCache{
    31  		cache: make(map[chat1.ConvIDStr]recentJoinsCacheItem),
    32  	}
    33  }
    34  
    35  func (i *recentJoinsMemCache) Get(convID chat1.ConversationID) int {
    36  	i.RLock()
    37  	defer i.RUnlock()
    38  	if item, ok := i.cache[convID.ConvIDStr()]; ok {
    39  		if time.Since(item.mtime.Time()) > time.Hour {
    40  			delete(i.cache, convID.ConvIDStr())
    41  			return -1
    42  		}
    43  		return item.numJoins
    44  	}
    45  	return -1
    46  }
    47  
    48  func (i *recentJoinsMemCache) Put(convID chat1.ConversationID, numJoins int) {
    49  	i.Lock()
    50  	defer i.Unlock()
    51  	i.cache[convID.ConvIDStr()] = recentJoinsCacheItem{
    52  		numJoins: numJoins,
    53  		mtime:    gregor1.ToTime(time.Now()),
    54  	}
    55  }
    56  
    57  func (i *recentJoinsMemCache) clearCache() {
    58  	i.Lock()
    59  	defer i.Unlock()
    60  	i.cache = make(map[chat1.ConvIDStr]recentJoinsCacheItem)
    61  }
    62  
    63  func (i *recentJoinsMemCache) OnLogout(mctx libkb.MetaContext) error {
    64  	i.clearCache()
    65  	return nil
    66  }
    67  
    68  func (i *recentJoinsMemCache) OnDbNuke(mctx libkb.MetaContext) error {
    69  	i.clearCache()
    70  	return nil
    71  }
    72  
    73  type lastActiveAtCacheItem struct {
    74  	lastActiveAt gregor1.Time
    75  	mtime        gregor1.Time
    76  }
    77  
    78  type lastActiveAtMemCache struct {
    79  	sync.RWMutex
    80  	// key: teamID||uid
    81  	cache map[string]lastActiveAtCacheItem
    82  }
    83  
    84  func newLastActiveAtMemCache() *lastActiveAtMemCache {
    85  	return &lastActiveAtMemCache{
    86  		cache: make(map[string]lastActiveAtCacheItem),
    87  	}
    88  }
    89  
    90  func (i *lastActiveAtMemCache) key(teamID keybase1.TeamID, uid gregor1.UID) string {
    91  	return fmt.Sprintf("%s:%s", teamID, uid)
    92  }
    93  
    94  func (i *lastActiveAtMemCache) Get(teamID keybase1.TeamID, uid gregor1.UID) (gregor1.Time, bool) {
    95  	i.RLock()
    96  	defer i.RUnlock()
    97  	if item, ok := i.cache[i.key(teamID, uid)]; ok {
    98  		if time.Since(item.mtime.Time()) > time.Hour {
    99  			delete(i.cache, i.key(teamID, uid))
   100  			return 0, false
   101  		}
   102  		return item.lastActiveAt, true
   103  	}
   104  	return 0, false
   105  }
   106  
   107  func (i *lastActiveAtMemCache) Put(teamID keybase1.TeamID, uid gregor1.UID, lastActiveAt gregor1.Time) {
   108  	i.Lock()
   109  	defer i.Unlock()
   110  	i.cache[i.key(teamID, uid)] = lastActiveAtCacheItem{
   111  		lastActiveAt: lastActiveAt,
   112  		mtime:        gregor1.ToTime(time.Now()),
   113  	}
   114  }
   115  
   116  func (i *lastActiveAtMemCache) clearCache() {
   117  	i.Lock()
   118  	defer i.Unlock()
   119  	i.cache = make(map[string]lastActiveAtCacheItem)
   120  }
   121  
   122  func (i *lastActiveAtMemCache) OnLogout(mctx libkb.MetaContext) error {
   123  	i.clearCache()
   124  	return nil
   125  }
   126  
   127  func (i *lastActiveAtMemCache) OnDbNuke(mctx libkb.MetaContext) error {
   128  	i.clearCache()
   129  	return nil
   130  }
   131  
   132  type TeamChannelSource struct {
   133  	sync.Mutex
   134  	globals.Contextified
   135  	utils.DebugLabeler
   136  	recentJoinsCache  *recentJoinsMemCache
   137  	lastActiveAtCache *lastActiveAtMemCache
   138  }
   139  
   140  var _ types.TeamChannelSource = (*TeamChannelSource)(nil)
   141  
   142  func NewTeamChannelSource(g *globals.Context) *TeamChannelSource {
   143  	return &TeamChannelSource{
   144  		Contextified:      globals.NewContextified(g),
   145  		DebugLabeler:      utils.NewDebugLabeler(g.ExternalG(), "TeamChannelSource", false),
   146  		recentJoinsCache:  newRecentJoinsMemCache(),
   147  		lastActiveAtCache: newLastActiveAtMemCache(),
   148  	}
   149  }
   150  
   151  func (c *TeamChannelSource) OnLogout(mctx libkb.MetaContext) error {
   152  	epick := libkb.FirstErrorPicker{}
   153  	epick.Push(c.recentJoinsCache.OnLogout(mctx))
   154  	epick.Push(c.lastActiveAtCache.OnLogout(mctx))
   155  	return epick.Error()
   156  }
   157  
   158  func (c *TeamChannelSource) OnDbNuke(mctx libkb.MetaContext) error {
   159  	epick := libkb.FirstErrorPicker{}
   160  	epick.Push(c.recentJoinsCache.OnDbNuke(mctx))
   161  	epick.Push(c.lastActiveAtCache.OnDbNuke(mctx))
   162  	return epick.Error()
   163  }
   164  
   165  func (c *TeamChannelSource) getTLFConversations(ctx context.Context, uid gregor1.UID,
   166  	tlfID chat1.TLFID, topicType chat1.TopicType) ([]types.RemoteConversation, error) {
   167  	inbox, err := c.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll,
   168  		&chat1.GetInboxQuery{
   169  			TlfID:            &tlfID,
   170  			TopicType:        &topicType,
   171  			SummarizeMaxMsgs: false,
   172  			MemberStatus:     chat1.AllConversationMemberStatuses(),
   173  			Existences:       []chat1.ConversationExistence{chat1.ConversationExistence_ACTIVE},
   174  			SkipBgLoads:      true,
   175  		})
   176  	return inbox.ConvsUnverified, err
   177  }
   178  
   179  func (c *TeamChannelSource) GetLastActiveForTLF(ctx context.Context, uid gregor1.UID,
   180  	tlfID chat1.TLFID, topicType chat1.TopicType) (res gregor1.Time, err error) {
   181  	defer c.Trace(ctx, &err,
   182  		fmt.Sprintf("GetLastActiveForTLF: tlfID: %v, topicType: %v", tlfID, topicType))()
   183  
   184  	rcs, err := c.getTLFConversations(ctx, uid, tlfID, topicType)
   185  	if err != nil {
   186  		return 0, err
   187  	}
   188  	sort.Sort(utils.RemoteConvByMtime(rcs))
   189  	if len(rcs) > 0 {
   190  		return utils.GetConvMtime(rcs[0]), nil
   191  	}
   192  	return 0, nil
   193  }
   194  
   195  func (c *TeamChannelSource) GetLastActiveForTeams(ctx context.Context, uid gregor1.UID, topicType chat1.TopicType) (
   196  	res chat1.LastActiveTimeAll, err error) {
   197  	ctx = globals.CtxModifyUnboxMode(ctx, types.UnboxModeQuick)
   198  	defer c.Trace(ctx, &err,
   199  		fmt.Sprintf("GetLastActiveForTeams: topicType: %v", topicType))()
   200  
   201  	inbox, err := c.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll,
   202  		&chat1.GetInboxQuery{
   203  			TopicType:        &topicType,
   204  			SummarizeMaxMsgs: false,
   205  			MemberStatus:     chat1.AllConversationMemberStatuses(),
   206  			Existences:       []chat1.ConversationExistence{chat1.ConversationExistence_ACTIVE},
   207  			MembersTypes:     []chat1.ConversationMembersType{chat1.ConversationMembersType_TEAM},
   208  			SkipBgLoads:      true,
   209  		})
   210  	byTLFID := make(map[chat1.TLFIDStr][]types.RemoteConversation)
   211  	channels := make(map[chat1.ConvIDStr]gregor1.Time, len(inbox.ConvsUnverified))
   212  	for _, conv := range inbox.ConvsUnverified {
   213  		rc := conv
   214  		tlfID := rc.Conv.Metadata.IdTriple.Tlfid.TLFIDStr()
   215  		byTLFID[tlfID] = append(byTLFID[tlfID], rc)
   216  		channels[rc.ConvIDStr] = utils.GetConvMtime(rc)
   217  	}
   218  	teams := make(map[chat1.TLFIDStr]gregor1.Time, len(byTLFID))
   219  	for tlfID, rcs := range byTLFID {
   220  		sort.Sort(utils.RemoteConvByMtime(rcs))
   221  		teams[tlfID] = channels[rcs[0].ConvIDStr]
   222  	}
   223  	res.Teams = teams
   224  	res.Channels = channels
   225  	return res, nil
   226  }
   227  
   228  func (c *TeamChannelSource) GetChannelsFull(ctx context.Context, uid gregor1.UID,
   229  	tlfID chat1.TLFID, topicType chat1.TopicType) (res []chat1.ConversationLocal, err error) {
   230  	ctx = globals.CtxModifyUnboxMode(ctx, types.UnboxModeQuick)
   231  	defer c.Trace(ctx, &err,
   232  		fmt.Sprintf("GetChannelsFull: tlfID: %v, topicType: %v", tlfID, topicType))()
   233  
   234  	rcs, err := c.getTLFConversations(ctx, uid, tlfID, topicType)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	for _, rc := range rcs {
   239  		c.G().ParticipantsSource.GetWithNotifyNonblock(ctx, uid, rc.GetConvID(),
   240  			types.InboxSourceDataSourceAll)
   241  	}
   242  	convs, _, err := c.G().InboxSource.Localize(ctx, uid, rcs, types.ConversationLocalizerBlocking)
   243  	if err != nil {
   244  		c.Debug(ctx, "GetChannelsFull: failed to localize conversations: %s", err.Error())
   245  		return nil, err
   246  	}
   247  	sort.Sort(utils.ConvLocalByTopicName(convs))
   248  	c.Debug(ctx, "GetChannelsFull: found %d convs", len(convs))
   249  	return convs, nil
   250  }
   251  
   252  func (c *TeamChannelSource) GetChannelsTopicName(ctx context.Context, uid gregor1.UID,
   253  	tlfID chat1.TLFID, topicType chat1.TopicType) (res []chat1.ChannelNameMention, err error) {
   254  	ctx = globals.CtxModifyUnboxMode(ctx, types.UnboxModeQuick)
   255  	defer c.Trace(ctx, &err,
   256  		fmt.Sprintf("GetChannelsTopicName: tlfID: %v, topicType: %v", tlfID, topicType))()
   257  
   258  	addValidMetadataMsg := func(convID chat1.ConversationID, msg chat1.MessageUnboxed) {
   259  		if !msg.IsValid() {
   260  			c.Debug(ctx, "GetChannelsTopicName: metadata message invalid: convID, %s", convID)
   261  			return
   262  		}
   263  		body := msg.Valid().MessageBody
   264  		typ, err := body.MessageType()
   265  		if err != nil {
   266  			c.Debug(ctx, "GetChannelsTopicName: error getting message type: convID, %s",
   267  				convID, err)
   268  			return
   269  		}
   270  		if typ != chat1.MessageType_METADATA {
   271  			c.Debug(ctx, "GetChannelsTopicName: message not a real metadata message: convID, %s msgID: %d",
   272  				convID, msg.GetMessageID())
   273  			return
   274  		}
   275  		res = append(res, chat1.ChannelNameMention{
   276  			ConvID:    convID,
   277  			TopicName: body.Metadata().ConversationTitle,
   278  		})
   279  	}
   280  
   281  	convs, err := c.getTLFConversations(ctx, uid, tlfID, topicType)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	for _, rc := range convs {
   286  		conv := rc.Conv
   287  		msg, err := conv.GetMaxMessage(chat1.MessageType_METADATA)
   288  		if err != nil {
   289  			continue
   290  		}
   291  		unboxeds, err := c.G().ConvSource.GetMessages(ctx, conv.GetConvID(), uid,
   292  			[]chat1.MessageID{msg.GetMessageID()}, nil, nil, false)
   293  		if err != nil {
   294  			c.Debug(ctx, "GetChannelsTopicName: failed to unbox metadata message for: convID: %s err: %s",
   295  				conv.GetConvID(), err)
   296  			continue
   297  		}
   298  		if len(unboxeds) != 1 {
   299  			c.Debug(ctx, "GetChannelsTopicName: empty result: convID: %s", conv.GetConvID())
   300  			continue
   301  		}
   302  		addValidMetadataMsg(conv.GetConvID(), unboxeds[0])
   303  	}
   304  	return res, nil
   305  }
   306  
   307  func (c *TeamChannelSource) GetChannelTopicName(ctx context.Context, uid gregor1.UID,
   308  	tlfID chat1.TLFID, topicType chat1.TopicType, convID chat1.ConversationID) (res string, err error) {
   309  	ctx = globals.CtxModifyUnboxMode(ctx, types.UnboxModeQuick)
   310  	defer c.Trace(ctx, &err,
   311  		fmt.Sprintf("GetChannelTopicName: tlfID: %v, topicType: %v, convID: %v", tlfID, topicType, convID))()
   312  
   313  	convs, err := c.GetChannelsTopicName(ctx, uid, tlfID, topicType)
   314  	if err != nil {
   315  		return "", err
   316  	}
   317  	if len(convs) == 0 {
   318  		return "", fmt.Errorf("no convs found")
   319  	}
   320  	for _, conv := range convs {
   321  		if conv.ConvID.Eq(convID) {
   322  			return conv.TopicName, nil
   323  		}
   324  	}
   325  	return "", fmt.Errorf("no convs found with convID")
   326  }
   327  
   328  func (c *TeamChannelSource) GetRecentJoins(ctx context.Context, convID chat1.ConversationID, remoteClient chat1.RemoteInterface) (res int, err error) {
   329  	defer c.Trace(ctx, &err, "GetRecentJoins")()
   330  
   331  	numJoins := c.recentJoinsCache.Get(convID)
   332  	if numJoins < 0 {
   333  		res, err := remoteClient.GetRecentJoins(ctx, convID)
   334  		if err != nil {
   335  			return 0, err
   336  		}
   337  		numJoins = res.NumJoins
   338  		c.recentJoinsCache.Put(convID, numJoins)
   339  	}
   340  	return numJoins, nil
   341  }
   342  
   343  func (c *TeamChannelSource) GetLastActiveAt(ctx context.Context, teamID keybase1.TeamID, uid gregor1.UID,
   344  	remoteClient chat1.RemoteInterface) (res gregor1.Time, err error) {
   345  	defer c.Trace(ctx, &err, "GetLastActiveAt")()
   346  
   347  	lastActiveAt, found := c.lastActiveAtCache.Get(teamID, uid)
   348  	if !found {
   349  		res, err := remoteClient.GetLastActiveAt(ctx, chat1.GetLastActiveAtArg{
   350  			TeamID: teamID,
   351  			Uid:    uid,
   352  		})
   353  		if err != nil {
   354  			return 0, err
   355  		}
   356  		lastActiveAt = res.LastActiveAt
   357  		c.lastActiveAtCache.Put(teamID, uid, lastActiveAt)
   358  	}
   359  	return lastActiveAt, nil
   360  }