github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/teammentionloader.go (about)

     1  package chat
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  
    10  	"github.com/keybase/client/go/chat/globals"
    11  	"github.com/keybase/client/go/chat/utils"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/chat1"
    14  	"github.com/keybase/client/go/protocol/gregor1"
    15  )
    16  
    17  type teamMentionJob struct {
    18  	uid               gregor1.UID
    19  	maybeMention      chat1.MaybeMention
    20  	knownTeamMentions []chat1.KnownTeamMention
    21  	forceRemote       bool
    22  }
    23  
    24  type TeamMentionLoader struct {
    25  	sync.Mutex
    26  	globals.Contextified
    27  	utils.DebugLabeler
    28  
    29  	started    bool
    30  	jobCh      chan teamMentionJob
    31  	shutdownCh chan chan struct{}
    32  }
    33  
    34  func NewTeamMentionLoader(g *globals.Context) *TeamMentionLoader {
    35  	return &TeamMentionLoader{
    36  		Contextified: globals.NewContextified(g),
    37  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "TeamMentionLoader", false),
    38  		jobCh:        make(chan teamMentionJob, 100),
    39  		shutdownCh:   make(chan chan struct{}, 5),
    40  	}
    41  }
    42  
    43  func (l *TeamMentionLoader) Start(ctx context.Context, uid gregor1.UID) {
    44  	defer l.Trace(ctx, nil, "Start")()
    45  	l.Lock()
    46  	defer l.Unlock()
    47  	if l.started {
    48  		return
    49  	}
    50  	l.started = true
    51  	go l.loadLoop()
    52  }
    53  
    54  func (l *TeamMentionLoader) Stop(ctx context.Context) chan struct{} {
    55  	defer l.Trace(ctx, nil, "Stop")()
    56  	l.Lock()
    57  	defer l.Unlock()
    58  	ch := make(chan struct{})
    59  	if l.started {
    60  		l.shutdownCh <- ch
    61  		l.started = false
    62  		return ch
    63  	}
    64  	close(ch)
    65  	return ch
    66  }
    67  
    68  func (l *TeamMentionLoader) IsTeamMention(ctx context.Context, uid gregor1.UID,
    69  	maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention) bool {
    70  	teamName, err := keybase1.TeamNameFromString(maybeMention.Name)
    71  	if err != nil {
    72  		return false
    73  	}
    74  	name := teamName.String()
    75  	for _, known := range knownTeamMentions {
    76  		if known.Name == name {
    77  			return true
    78  		}
    79  	}
    80  	res, err := l.G().InboxSource.IsTeam(ctx, uid, name)
    81  	if err != nil {
    82  		l.Debug(ctx, "isTeam: failed to check if team: %s", err)
    83  		return false
    84  	}
    85  	return res
    86  }
    87  
    88  func (l *TeamMentionLoader) LoadTeamMention(ctx context.Context, uid gregor1.UID,
    89  	maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention, forceRemote bool) (err error) {
    90  	defer l.Trace(ctx, &err, "LoadTeamMention")()
    91  	select {
    92  	case l.jobCh <- teamMentionJob{
    93  		uid:               uid,
    94  		maybeMention:      maybeMention,
    95  		knownTeamMentions: knownTeamMentions,
    96  		forceRemote:       forceRemote,
    97  	}:
    98  	default:
    99  		l.Debug(ctx, "Load: failed to queue job, full")
   100  		return errors.New("queue full")
   101  	}
   102  	return nil
   103  }
   104  
   105  type mentionAPIResp struct {
   106  	Status       libkb.AppStatus `json:"status"`
   107  	Name         string
   108  	InTeam       bool `json:"in_team"`
   109  	Open         bool
   110  	Description  string
   111  	PublicAdmins []string `json:"public_admins"`
   112  	NumMembers   int      `json:"num_members"`
   113  }
   114  
   115  func (r *mentionAPIResp) GetAppStatus() *libkb.AppStatus {
   116  	return &r.Status
   117  }
   118  
   119  func (l *TeamMentionLoader) getChatUI(ctx context.Context) (libkb.ChatUI, error) {
   120  	ui, err := l.G().UIRouter.GetChatUI()
   121  	if err != nil || ui == nil {
   122  		l.Debug(ctx, "getChatUI: no chat UI found: err: %s", err)
   123  		if err == nil {
   124  			err = errors.New("no chat UI found")
   125  		}
   126  		return nil, err
   127  	}
   128  	return ui, nil
   129  }
   130  
   131  func (l *TeamMentionLoader) loadMention(ctx context.Context, uid gregor1.UID,
   132  	maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention,
   133  	forceRemote bool) (err error) {
   134  	defer l.Trace(ctx, &err, "loadTeamMention: name: %s", maybeMention.Name)()
   135  	ui, err := l.getChatUI(ctx)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if _, err := keybase1.TeamNameFromString(maybeMention.Name); err != nil {
   140  		_ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel,
   141  			chat1.NewUIMaybeMentionInfoWithNothing())
   142  		return errors.New("not a team string")
   143  	}
   144  	if !forceRemote && !l.IsTeamMention(ctx, uid, maybeMention, knownTeamMentions) {
   145  		_ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel,
   146  			chat1.NewUIMaybeMentionInfoWithUnknown())
   147  		return errors.New("not a team mention")
   148  	}
   149  
   150  	var info chat1.UITeamMention
   151  	arg := libkb.APIArg{
   152  		Endpoint:    "team/mentiondesc",
   153  		SessionType: libkb.APISessionTypeREQUIRED,
   154  		Args:        libkb.HTTPArgs{"name": libkb.S{Val: maybeMention.Name}},
   155  	}
   156  	var resp mentionAPIResp
   157  	if err = l.G().API.GetDecode(libkb.NewMetaContext(ctx, l.G().ExternalG()), arg, &resp); err != nil {
   158  		l.Debug(ctx, "loadMention: failed to get team info: %s", err)
   159  		_ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel,
   160  			chat1.NewUIMaybeMentionInfoWithNothing())
   161  		return err
   162  	}
   163  	info.Open = resp.Open
   164  	info.InTeam = resp.InTeam
   165  	if len(resp.Description) > 0 {
   166  		info.Description = new(string)
   167  		*info.Description = resp.Description
   168  	}
   169  	if resp.NumMembers > 0 {
   170  		info.NumMembers = new(int)
   171  		*info.NumMembers = resp.NumMembers
   172  	}
   173  	info.PublicAdmins = resp.PublicAdmins
   174  
   175  	if info.InTeam {
   176  		var channel *string
   177  		if len(maybeMention.Channel) > 0 {
   178  			channel = new(string)
   179  			*channel = maybeMention.Channel
   180  		}
   181  		convs, err := l.G().ChatHelper.FindConversations(ctx, maybeMention.Name, channel,
   182  			chat1.TopicType_CHAT, chat1.ConversationMembersType_TEAM, keybase1.TLFVisibility_PRIVATE)
   183  		if err != nil || len(convs) == 0 {
   184  			l.Debug(ctx, "loadMention: failed to find conversation: %v", err)
   185  		} else {
   186  			info.ConvID = new(chat1.ConvIDStr)
   187  			*info.ConvID = convs[0].GetConvID().ConvIDStr()
   188  		}
   189  	}
   190  	return ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel,
   191  		chat1.NewUIMaybeMentionInfoWithTeam(info))
   192  }
   193  
   194  func (l *TeamMentionLoader) loadLoop() {
   195  	ctx := context.Background()
   196  	for {
   197  		select {
   198  		case job := <-l.jobCh:
   199  			_ = l.loadMention(ctx, job.uid, job.maybeMention, job.knownTeamMentions, job.forceRemote)
   200  		case ch := <-l.shutdownCh:
   201  			close(ch)
   202  			return
   203  		}
   204  	}
   205  }