github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/inboxsource.go (about) 1 package chat 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/keybase/client/go/chat/globals" 12 "github.com/keybase/client/go/chat/storage" 13 "github.com/keybase/client/go/chat/types" 14 "github.com/keybase/client/go/chat/utils" 15 "github.com/keybase/client/go/libkb" 16 "github.com/keybase/client/go/protocol/chat1" 17 "github.com/keybase/client/go/protocol/gregor1" 18 "github.com/keybase/client/go/protocol/keybase1" 19 "github.com/keybase/client/go/teams/opensearch" 20 context "golang.org/x/net/context" 21 "golang.org/x/sync/errgroup" 22 ) 23 24 func filterConvLocals(convLocals []chat1.ConversationLocal, rquery *chat1.GetInboxQuery, 25 query *chat1.GetInboxLocalQuery, nameInfo types.NameInfo) (res []chat1.ConversationLocal, err error) { 26 res = make([]chat1.ConversationLocal, 0, len(convLocals)) 27 for _, convLocal := range convLocals { 28 if rquery != nil && rquery.TlfID != nil { 29 // inbox query contained a TLF name, so check to make sure that 30 // the conversation from the server matches tlfInfo from kbfs 31 if len(nameInfo.CanonicalName) > 0 && 32 convLocal.Info.TLFNameExpanded() != nameInfo.CanonicalName { 33 if convLocal.Error == nil { 34 return nil, fmt.Errorf("server conversation TLF name mismatch: %s, expected %s", 35 convLocal.Info.TLFNameExpanded(), nameInfo.CanonicalName) 36 } 37 } 38 if convLocal.Info.Visibility != rquery.Visibility() { 39 return nil, fmt.Errorf("server conversation TLF visibility mismatch: %s, expected %s", 40 convLocal.Info.Visibility, rquery.Visibility()) 41 } 42 if !nameInfo.ID.Eq(convLocal.Info.Triple.Tlfid) { 43 return nil, fmt.Errorf("server conversation TLF ID mismatch: %s, expected %s", 44 convLocal.Info.Triple.Tlfid, nameInfo.ID) 45 } 46 // tlfInfo.ID and rquery.TlfID should always match, but just in case: 47 if !rquery.TlfID.Eq(convLocal.Info.Triple.Tlfid) { 48 return nil, fmt.Errorf("server conversation TLF ID mismatch: %s, expected %s", 49 convLocal.Info.Triple.Tlfid, rquery.TlfID) 50 } 51 52 // Note that previously, we made a call to KBFS to lookup the TLF in 53 // convLocal.Info.TlfName and verify that, but the above checks accomplish 54 // the same thing without an RPC call. 55 } 56 57 // server can't query on topic name, so we have to do it ourselves in the loop 58 if query != nil && query.TopicName != nil && *query.TopicName != convLocal.Info.TopicName { 59 continue 60 } 61 62 res = append(res, convLocal) 63 } 64 65 return res, nil 66 } 67 68 type baseInboxSource struct { 69 globals.Contextified 70 utils.DebugLabeler 71 72 so *sourceOfflinable 73 sub types.InboxSource 74 getChatInterface func() chat1.RemoteInterface 75 localizer *localizerPipeline 76 } 77 78 func newBaseInboxSource(g *globals.Context, ibs types.InboxSource, 79 getChatInterface func() chat1.RemoteInterface) *baseInboxSource { 80 labeler := utils.NewDebugLabeler(g.ExternalG(), "baseInboxSource", false) 81 return &baseInboxSource{ 82 Contextified: globals.NewContextified(g), 83 sub: ibs, 84 DebugLabeler: labeler, 85 getChatInterface: getChatInterface, 86 so: newSourceOfflinable(g, labeler), 87 localizer: newLocalizerPipeline(g), 88 } 89 } 90 91 func (b *baseInboxSource) notifyTlfFinalize(ctx context.Context, username string) { 92 // Let the rest of the system know this user has changed 93 arg := libkb.NewLoadUserArg(b.G().ExternalG()).WithName(username).WithPublicKeyOptional() 94 finalizeUser, err := libkb.LoadUser(arg) 95 if err != nil { 96 b.Debug(ctx, "notifyTlfFinalize: failed to load finalize user, skipping user changed notification: err: %s", err.Error()) 97 } else { 98 b.G().UserChanged(ctx, finalizeUser.GetUID()) 99 } 100 } 101 102 func (b *baseInboxSource) SetRemoteInterface(ri func() chat1.RemoteInterface) { 103 b.getChatInterface = ri 104 } 105 106 func (b *baseInboxSource) GetInboxQueryLocalToRemote(ctx context.Context, 107 lquery *chat1.GetInboxLocalQuery) (rquery *chat1.GetInboxQuery, info types.NameInfo, err error) { 108 109 if lquery == nil { 110 return nil, info, nil 111 } 112 113 rquery = &chat1.GetInboxQuery{} 114 if lquery.Name != nil && lquery.Name.TlfID != nil && len(lquery.Name.Name) > 0 { 115 rquery.TlfID = lquery.Name.TlfID 116 rquery.MembersTypes = []chat1.ConversationMembersType{lquery.Name.MembersType} 117 info = types.NameInfo{ 118 CanonicalName: lquery.Name.Name, 119 ID: *lquery.Name.TlfID, 120 } 121 b.Debug(ctx, "GetInboxQueryLocalToRemote: using TLFID: %v", *lquery.Name.TlfID) 122 } else if lquery.Name != nil && lquery.Name.TlfID != nil { 123 rquery.TlfID = lquery.Name.TlfID 124 info = types.NameInfo{ 125 ID: *lquery.Name.TlfID, 126 } 127 b.Debug(ctx, "GetInboxQueryLocalToRemote: using TLFID (nameless): %v", *lquery.Name.TlfID) 128 } else if lquery.Name != nil && len(lquery.Name.Name) > 0 { 129 var err error 130 tlfName := utils.AddUserToTLFName(b.G(), lquery.Name.Name, lquery.Visibility(), 131 lquery.Name.MembersType) 132 info, err = CreateNameInfoSource(ctx, b.G(), lquery.Name.MembersType).LookupID(ctx, tlfName, 133 lquery.Visibility() == keybase1.TLFVisibility_PUBLIC) 134 if err != nil { 135 b.Debug(ctx, "GetInboxQueryLocalToRemote: failed: %s", err) 136 return nil, info, err 137 } 138 rquery.TlfID = &info.ID 139 rquery.MembersTypes = []chat1.ConversationMembersType{lquery.Name.MembersType} 140 b.Debug(ctx, "GetInboxQueryLocalToRemote: mapped name %q to TLFID %v", tlfName, info.ID) 141 } 142 rquery.TopicName = lquery.TopicName 143 rquery.After = lquery.After 144 rquery.Before = lquery.Before 145 rquery.TlfVisibility = lquery.TlfVisibility 146 rquery.TopicType = lquery.TopicType 147 rquery.UnreadOnly = lquery.UnreadOnly 148 rquery.ReadOnly = lquery.ReadOnly 149 rquery.ComputeActiveList = lquery.ComputeActiveList 150 rquery.ConvIDs = lquery.ConvIDs 151 rquery.OneChatTypePerTLF = lquery.OneChatTypePerTLF 152 rquery.Status = lquery.Status 153 rquery.MemberStatus = lquery.MemberStatus 154 rquery.SummarizeMaxMsgs = false 155 156 return rquery, info, nil 157 } 158 159 func (b *baseInboxSource) IsMember(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (bool, error) { 160 conv, err := utils.GetUnverifiedConv(ctx, b.G(), uid, convID, types.InboxSourceDataSourceAll) 161 if err != nil { 162 return false, err 163 } 164 switch conv.Conv.ReaderInfo.Status { 165 case chat1.ConversationMemberStatus_ACTIVE, chat1.ConversationMemberStatus_RESET: 166 return true, nil 167 default: 168 return false, nil 169 } 170 } 171 172 func (b *baseInboxSource) Localize(ctx context.Context, uid gregor1.UID, convs []types.RemoteConversation, 173 localizerTyp types.ConversationLocalizerTyp) ([]chat1.ConversationLocal, chan types.AsyncInboxResult, error) { 174 localizeCb := make(chan types.AsyncInboxResult, len(convs)) 175 localizer := b.createConversationLocalizer(ctx, localizerTyp, localizeCb) 176 b.Debug(ctx, "Localize: using localizer: %s, convs: %d", localizer.Name(), len(convs)) 177 178 res, err := localizer.Localize(ctx, uid, types.Inbox{ 179 ConvsUnverified: convs, 180 }, nil) 181 return res, localizeCb, err 182 } 183 184 func (b *baseInboxSource) RemoteSetConversationStatus(ctx context.Context, uid gregor1.UID, 185 convID chat1.ConversationID, status chat1.ConversationStatus) (err error) { 186 defer b.Trace(ctx, &err, "RemoteSetConversationStatus")() 187 if _, err = b.getChatInterface().SetConversationStatus(ctx, chat1.SetConversationStatusArg{ 188 ConversationID: convID, 189 Status: status, 190 }); err != nil { 191 return err 192 } 193 return nil 194 } 195 196 func (b *baseInboxSource) RemoteDeleteConversation(ctx context.Context, uid gregor1.UID, 197 convID chat1.ConversationID) (err error) { 198 defer b.Trace(ctx, &err, "RemoteDeleteConversation")() 199 if _, err = b.getChatInterface().DeleteConversation(ctx, convID); err != nil { 200 return err 201 } 202 return nil 203 } 204 205 func (b *baseInboxSource) createConversationLocalizer(ctx context.Context, typ types.ConversationLocalizerTyp, 206 localizeCb chan types.AsyncInboxResult) conversationLocalizer { 207 switch typ { 208 case types.ConversationLocalizerBlocking: 209 return newBlockingLocalizer(b.G(), b.localizer, localizeCb) 210 case types.ConversationLocalizerNonblocking: 211 return newNonblockingLocalizer(b.G(), b.localizer, localizeCb) 212 default: 213 b.Debug(ctx, "createConversationLocalizer: warning unknown typ %v, using blockingLocalizer as default", typ) 214 return newBlockingLocalizer(b.G(), b.localizer, localizeCb) 215 } 216 } 217 218 func (b *baseInboxSource) setDefaultParticipantMode(q *chat1.GetInboxQuery) *chat1.GetInboxQuery { 219 if q == nil { 220 q = new(chat1.GetInboxQuery) 221 } 222 q.ParticipantsMode = chat1.InboxParticipantsMode_SKIP_TEAMS 223 return q 224 } 225 226 func (b *baseInboxSource) Start(ctx context.Context, uid gregor1.UID) { 227 b.localizer.start(ctx) 228 } 229 230 func (b *baseInboxSource) Stop(ctx context.Context) chan struct{} { 231 return b.localizer.stop(ctx) 232 } 233 234 func (b *baseInboxSource) Suspend(ctx context.Context) bool { 235 return b.localizer.suspend(ctx) 236 } 237 238 func (b *baseInboxSource) Resume(ctx context.Context) bool { 239 return b.localizer.resume(ctx) 240 } 241 242 func (b *baseInboxSource) IsOffline(ctx context.Context) bool { 243 return b.so.IsOffline(ctx) 244 } 245 246 func (b *baseInboxSource) Connected(ctx context.Context) { 247 b.so.Connected(ctx) 248 b.localizer.Connected() 249 } 250 251 func (b *baseInboxSource) Disconnected(ctx context.Context) { 252 b.so.Disconnected(ctx) 253 b.localizer.Disconnected() 254 } 255 256 func (b *baseInboxSource) ApplyLocalChatState(ctx context.Context, i []keybase1.BadgeConversationInfo) ([]keybase1.BadgeConversationInfo, int, int) { 257 return i, 0, 0 258 } 259 260 func GetInboxQueryNameInfo(ctx context.Context, g *globals.Context, 261 lquery *chat1.GetInboxLocalQuery) (res types.NameInfo, err error) { 262 if lquery.Name == nil { 263 return res, errors.New("invalid name query") 264 } else if lquery.Name != nil && len(lquery.Name.Name) > 0 { 265 if lquery.Name.TlfID != nil { 266 return CreateNameInfoSource(ctx, g, lquery.Name.MembersType).LookupName(ctx, *lquery.Name.TlfID, 267 lquery.Visibility() == keybase1.TLFVisibility_PUBLIC, lquery.Name.Name) 268 } 269 return CreateNameInfoSource(ctx, g, lquery.Name.MembersType).LookupID(ctx, lquery.Name.Name, 270 lquery.Visibility() == keybase1.TLFVisibility_PUBLIC) 271 } else { 272 return res, errors.New("invalid name query") 273 } 274 } 275 276 type RemoteInboxSource struct { 277 globals.Contextified 278 utils.DebugLabeler 279 *baseInboxSource 280 } 281 282 var _ types.InboxSource = (*RemoteInboxSource)(nil) 283 284 func NewRemoteInboxSource(g *globals.Context, ri func() chat1.RemoteInterface) *RemoteInboxSource { 285 labeler := utils.NewDebugLabeler(g.ExternalG(), "RemoteInboxSource", false) 286 s := &RemoteInboxSource{ 287 Contextified: globals.NewContextified(g), 288 DebugLabeler: labeler, 289 } 290 s.baseInboxSource = newBaseInboxSource(g, s, ri) 291 return s 292 } 293 294 func (s *RemoteInboxSource) Clear(ctx context.Context, uid gregor1.UID, opts *types.ClearOpts) error { 295 return nil 296 } 297 298 func (s *RemoteInboxSource) Read(ctx context.Context, uid gregor1.UID, 299 localizerTyp types.ConversationLocalizerTyp, dataSource types.InboxSourceDataSourceTyp, maxLocalize *int, 300 query *chat1.GetInboxLocalQuery) (types.Inbox, chan types.AsyncInboxResult, error) { 301 302 rquery, tlfInfo, err := s.GetInboxQueryLocalToRemote(ctx, query) 303 if err != nil { 304 return types.Inbox{}, nil, err 305 } 306 inbox, err := s.ReadUnverified(ctx, uid, dataSource, rquery) 307 if err != nil { 308 return types.Inbox{}, nil, err 309 } 310 311 localizeCb := make(chan types.AsyncInboxResult, len(inbox.ConvsUnverified)) 312 localizer := s.createConversationLocalizer(ctx, localizerTyp, localizeCb) 313 s.Debug(ctx, "Read: using localizer: %s", localizer.Name()) 314 315 res, err := localizer.Localize(ctx, uid, inbox, maxLocalize) 316 if err != nil { 317 return types.Inbox{}, localizeCb, err 318 } 319 320 res, err = filterConvLocals(res, rquery, query, tlfInfo) 321 if err != nil { 322 return types.Inbox{}, localizeCb, err 323 } 324 325 return types.Inbox{ 326 Version: inbox.Version, 327 Convs: res, 328 ConvsUnverified: inbox.ConvsUnverified, 329 }, localizeCb, nil 330 } 331 332 func (s *RemoteInboxSource) ReadUnverified(ctx context.Context, uid gregor1.UID, 333 dataSource types.InboxSourceDataSourceTyp, rquery *chat1.GetInboxQuery) (types.Inbox, error) { 334 if s.IsOffline(ctx) { 335 return types.Inbox{}, OfflineError{} 336 } 337 ib, err := s.getChatInterface().GetInboxRemote(ctx, chat1.GetInboxRemoteArg{ 338 Query: s.setDefaultParticipantMode(rquery), 339 }) 340 if err != nil { 341 return types.Inbox{}, err 342 } 343 return types.Inbox{ 344 Version: ib.Inbox.Full().Vers, 345 ConvsUnverified: utils.RemoteConvs(ib.Inbox.Full().Conversations), 346 }, nil 347 } 348 349 func (s *RemoteInboxSource) MarkAsRead(ctx context.Context, convID chat1.ConversationID, 350 uid gregor1.UID, msgID *chat1.MessageID, forceUnread bool) (err error) { 351 defer s.Trace(ctx, &err, "MarkAsRead(%s,%v,%v)", convID, msgID, forceUnread)() 352 if msgID == nil { 353 conv, err := utils.GetUnverifiedConv(ctx, s.G(), uid, convID, types.InboxSourceDataSourceAll) 354 if err != nil { 355 return err 356 } 357 msgID = new(chat1.MessageID) 358 *msgID = conv.Conv.ReaderInfo.MaxMsgid 359 } 360 if _, err = s.getChatInterface().MarkAsRead(ctx, chat1.MarkAsReadArg{ 361 ConversationID: convID, 362 MsgID: *msgID, 363 ForceUnread: forceUnread, 364 }); err != nil { 365 return err 366 } 367 return nil 368 } 369 370 func (s *RemoteInboxSource) Search(ctx context.Context, uid gregor1.UID, query string, limit int, 371 emptyMode types.InboxSourceSearchEmptyMode) (res []types.RemoteConversation, err error) { 372 return nil, errors.New("not implemented") 373 } 374 375 func (s *RemoteInboxSource) IsTeam(ctx context.Context, uid gregor1.UID, item string) (bool, error) { 376 return false, errors.New("not implemented") 377 } 378 379 func (s *RemoteInboxSource) NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 380 conv chat1.Conversation) error { 381 return nil 382 } 383 384 func (s *RemoteInboxSource) Sync(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convs []chat1.Conversation) (res types.InboxSyncRes, err error) { 385 return res, nil 386 } 387 388 func (s *RemoteInboxSource) NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 389 convID chat1.ConversationID, msg chat1.MessageBoxed, maxMsgs []chat1.MessageSummary) (*chat1.ConversationLocal, error) { 390 return nil, nil 391 } 392 393 func (s *RemoteInboxSource) ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 394 convID chat1.ConversationID, msgID chat1.MessageID) (*chat1.ConversationLocal, error) { 395 return nil, nil 396 } 397 398 func (s *RemoteInboxSource) SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 399 convID chat1.ConversationID, status chat1.ConversationStatus) (*chat1.ConversationLocal, error) { 400 return nil, nil 401 } 402 403 func (s *RemoteInboxSource) SetAppNotificationSettings(ctx context.Context, uid gregor1.UID, 404 vers chat1.InboxVers, convID chat1.ConversationID, settings chat1.ConversationNotificationInfo) (*chat1.ConversationLocal, error) { 405 return nil, nil 406 } 407 408 func (s *RemoteInboxSource) TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 409 convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) ([]chat1.ConversationLocal, error) { 410 // Notify rest of system about reset 411 s.notifyTlfFinalize(ctx, finalizeInfo.ResetUser) 412 return nil, nil 413 } 414 415 func (s *RemoteInboxSource) MembershipUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 416 joined []chat1.ConversationMember, removed []chat1.ConversationMember, resets []chat1.ConversationMember, 417 previews []chat1.ConversationID, teamMemberRoleUpdate *chat1.TeamMemberRoleUpdate) (res types.MembershipUpdateRes, err error) { 418 return res, err 419 } 420 421 func (s *RemoteInboxSource) ConversationsUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 422 convUpdates []chat1.ConversationUpdate) error { 423 return nil 424 } 425 426 func (s *RemoteInboxSource) Expunge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID, 427 expunge chat1.Expunge, maxMsgs []chat1.MessageSummary) (res *chat1.ConversationLocal, err error) { 428 return res, err 429 } 430 431 func (s *RemoteInboxSource) SetConvRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 432 convID chat1.ConversationID, policy chat1.RetentionPolicy) (res *chat1.ConversationLocal, err error) { 433 return res, err 434 } 435 436 func (s *RemoteInboxSource) SetTeamRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 437 teamID keybase1.TeamID, policy chat1.RetentionPolicy) (res []chat1.ConversationLocal, err error) { 438 return res, err 439 } 440 441 func (s *RemoteInboxSource) SetConvSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 442 convID chat1.ConversationID, convSettings *chat1.ConversationSettings) (res *chat1.ConversationLocal, err error) { 443 return res, err 444 } 445 446 func (s *RemoteInboxSource) SubteamRename(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 447 convIDs []chat1.ConversationID) (convs []chat1.ConversationLocal, err error) { 448 return convs, err 449 } 450 451 func (s *RemoteInboxSource) TeamTypeChanged(ctx context.Context, uid gregor1.UID, 452 vers chat1.InboxVers, convID chat1.ConversationID, teamType chat1.TeamType) (conv *chat1.ConversationLocal, err error) { 453 return conv, err 454 } 455 456 func (s *RemoteInboxSource) UpgradeKBFSToImpteam(ctx context.Context, uid gregor1.UID, 457 vers chat1.InboxVers, convID chat1.ConversationID) (conv *chat1.ConversationLocal, err error) { 458 return conv, err 459 } 460 461 func (s *RemoteInboxSource) UpdateInboxVersion(ctx context.Context, uid gregor1.UID, 462 vers chat1.InboxVers) error { 463 return nil 464 } 465 466 func (s *RemoteInboxSource) Draft(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 467 text *string) error { 468 return nil 469 } 470 471 func (s *RemoteInboxSource) MergeLocalMetadata(ctx context.Context, uid gregor1.UID, 472 convs []chat1.ConversationLocal) error { 473 return nil 474 } 475 476 func (s *RemoteInboxSource) NotifyUpdate(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) { 477 } 478 479 func (s *RemoteInboxSource) IncrementLocalConvVersion(ctx context.Context, uid gregor1.UID, 480 convID chat1.ConversationID) (conv *chat1.ConversationLocal, err error) { 481 return nil, nil 482 } 483 484 func (s *RemoteInboxSource) UpdateLocalMtime(ctx context.Context, uid gregor1.UID, updates []chat1.LocalMtimeUpdate) error { 485 return nil 486 } 487 488 func (s *RemoteInboxSource) TeamBotSettingsForConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) ( 489 map[keybase1.UID]keybase1.TeamBotSettings, error) { 490 return nil, nil 491 } 492 493 type HybridInboxSource struct { 494 sync.Mutex 495 globals.Contextified 496 utils.DebugLabeler 497 *baseInboxSource 498 499 uid gregor1.UID 500 started bool 501 stopCh chan struct{} 502 eg errgroup.Group 503 readOutbox *storage.ReadOutbox 504 readFlushDelay time.Duration 505 readFlushCh chan struct{} 506 searchStatusMap map[chat1.ConversationStatus]bool 507 searchMemberStatusMap map[chat1.ConversationMemberStatus]bool 508 } 509 510 var _ types.InboxSource = (*HybridInboxSource)(nil) 511 512 func NewHybridInboxSource(g *globals.Context, 513 getChatInterface func() chat1.RemoteInterface) *HybridInboxSource { 514 labeler := utils.NewDebugLabeler(g.ExternalG(), "HybridInboxSource", false) 515 s := &HybridInboxSource{ 516 Contextified: globals.NewContextified(g), 517 DebugLabeler: labeler, 518 readFlushDelay: 5 * time.Second, 519 readFlushCh: make(chan struct{}, 10), 520 } 521 s.searchStatusMap = map[chat1.ConversationStatus]bool{ 522 chat1.ConversationStatus_UNFILED: true, 523 chat1.ConversationStatus_FAVORITE: true, 524 chat1.ConversationStatus_MUTED: true, 525 chat1.ConversationStatus_IGNORED: true, 526 } 527 s.searchMemberStatusMap = map[chat1.ConversationMemberStatus]bool{ 528 chat1.ConversationMemberStatus_ACTIVE: true, 529 chat1.ConversationMemberStatus_PREVIEW: true, 530 chat1.ConversationMemberStatus_RESET: true, 531 } 532 s.baseInboxSource = newBaseInboxSource(g, s, getChatInterface) 533 return s 534 } 535 536 func (s *HybridInboxSource) maybeNuke(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID, err *error) { 537 if err != nil && utils.IsDeletedConvError(*err) { 538 s.Debug(ctx, "purging caches on: %v for convID: %v, uid: %v", *err, convID, uid) 539 if ierr := s.G().InboxSource.Clear(ctx, uid, &types.ClearOpts{ 540 SendLocalAdminNotification: true, 541 Reason: "Got unexpected conversation deleted error. Cleared conv and inbox cache", 542 }); ierr != nil { 543 s.Debug(ctx, "unable to Clear inbox: %v", ierr) 544 } 545 if convID != nil { 546 if ierr := s.G().ConvSource.Clear(ctx, *convID, uid, nil); ierr != nil { 547 s.Debug(ctx, "unable to Clear conv: %v", ierr) 548 } 549 } 550 s.G().UIInboxLoader.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "InboxSource#maybeNuke") 551 *err = nil 552 } 553 } 554 555 func (s *HybridInboxSource) createInbox() *storage.Inbox { 556 return storage.NewInbox(s.G(), 557 storage.LayoutChangedNotifier(s.G().UIInboxLoader)) 558 } 559 560 func (s *HybridInboxSource) Clear(ctx context.Context, uid gregor1.UID, opts *types.ClearOpts) (err error) { 561 defer s.Trace(ctx, &err, "Clear(%v)", uid)() 562 defer s.PerfTrace(ctx, &err, "Clear(%v)", uid)() 563 start := time.Now() 564 defer func() { 565 var message string 566 if err == nil { 567 message = fmt.Sprintf("Clearing inbox for %s", uid) 568 } else { 569 message = fmt.Sprintf("Failed to clear inbox %s", uid) 570 } 571 s.G().RuntimeStats.PushPerfEvent(keybase1.PerfEvent{ 572 EventType: keybase1.PerfEventType_CLEARINBOX, 573 Message: message, 574 Ctime: keybase1.ToTime(start), 575 }) 576 }() 577 kuid := keybase1.UID(uid.String()) 578 if (s.G().Env.GetRunMode() == libkb.DevelRunMode || libkb.IsKeybaseAdmin(kuid)) && 579 s.G().UIRouter != nil && opts != nil && opts.SendLocalAdminNotification { 580 ui, err := s.G().UIRouter.GetLogUI() 581 if err == nil && ui != nil { 582 ui.Critical("Clearing inbox: %s", opts.Reason) 583 } 584 } 585 586 return s.createInbox().Clear(ctx, uid) 587 } 588 589 func (s *HybridInboxSource) Connected(ctx context.Context) { 590 defer s.Trace(ctx, nil, "Connected")() 591 s.baseInboxSource.Connected(ctx) 592 s.flushMarkAsRead(ctx) 593 } 594 595 func (s *HybridInboxSource) Start(ctx context.Context, uid gregor1.UID) { 596 defer s.Trace(ctx, nil, "Start")() 597 s.baseInboxSource.Start(ctx, uid) 598 s.Lock() 599 defer s.Unlock() 600 if s.started { 601 return 602 } 603 s.stopCh = make(chan struct{}) 604 s.started = true 605 s.uid = uid 606 s.readOutbox = storage.NewReadOutbox(s.G(), uid) 607 s.eg.Go(func() error { return s.markAsReadDeliverLoop(uid, s.stopCh) }) 608 } 609 610 func (s *HybridInboxSource) Stop(ctx context.Context) chan struct{} { 611 defer s.Trace(ctx, nil, "Stop")() 612 <-s.baseInboxSource.Stop(ctx) 613 s.Lock() 614 defer s.Unlock() 615 ch := make(chan struct{}) 616 if s.started { 617 close(s.stopCh) 618 s.started = false 619 go func() { 620 _ = s.eg.Wait() 621 close(ch) 622 }() 623 } else { 624 close(ch) 625 } 626 return ch 627 } 628 629 func (s *HybridInboxSource) flushMarkAsRead(ctx context.Context) { 630 select { 631 case s.readFlushCh <- struct{}{}: 632 default: 633 s.Debug(ctx, "flushMarkAsRead: channel full, dropping") 634 } 635 } 636 637 func (s *HybridInboxSource) markAsReadDeliver(ctx context.Context) (err error) { 638 defer func() { 639 if err != nil { 640 s.Debug(ctx, "markAsReadDeliver: failed to mark as read: %s", err) 641 } 642 }() 643 recs, err := s.readOutbox.GetRecords(ctx) 644 if err != nil { 645 return err 646 } 647 for _, rec := range recs { 648 shouldRemove := false 649 if _, err := s.getChatInterface().MarkAsRead(ctx, chat1.MarkAsReadArg{ 650 ConversationID: rec.ConvID, 651 MsgID: rec.MsgID, 652 ForceUnread: rec.ForceUnread, 653 }); err != nil { 654 s.Debug(ctx, "markAsReadDeliver: failed to mark as read: convID: %s msgID: %s forceUnread: %v err: %s", 655 rec.ConvID, rec.MsgID, rec.ForceUnread, err) 656 // check for an immediate failure from the server, and get the attempt out if it fails 657 if berr, ok := err.(DelivererInfoError); ok { 658 if _, ok := berr.IsImmediateFail(); ok { 659 s.Debug(ctx, "markAsReadDeliver: error is an immediate failure, not retrying") 660 shouldRemove = true 661 } 662 } 663 } else { 664 shouldRemove = true 665 } 666 if shouldRemove { 667 if err := s.readOutbox.RemoveRecord(ctx, rec.ID); err != nil { 668 s.Debug(ctx, "markAsReadDeliver: failed to remove record: %s", err) 669 } 670 } 671 } 672 return nil 673 } 674 675 func (s *HybridInboxSource) markAsReadDeliverLoop(uid gregor1.UID, stopCh chan struct{}) error { 676 ctx := context.Background() 677 for { 678 select { 679 case <-s.readFlushCh: 680 if err := s.markAsReadDeliver(ctx); err != nil { 681 s.Debug(ctx, "unable to mark as read: %v", err) 682 } 683 case <-s.G().Clock().After(s.readFlushDelay): 684 if err := s.markAsReadDeliver(ctx); err != nil { 685 s.Debug(ctx, "unable to mark as read: %v", err) 686 } 687 case <-stopCh: 688 return nil 689 } 690 } 691 } 692 693 func makeBadgeConversationInfo(convID keybase1.ChatConversationID, count int) keybase1.BadgeConversationInfo { 694 return keybase1.BadgeConversationInfo{ 695 ConvID: convID, 696 BadgeCount: count, 697 UnreadMessages: count, 698 } 699 } 700 701 // ApplyLocalChatState marks items locally as read and badges conversations 702 // that have failed outbox items. 703 func (s *HybridInboxSource) ApplyLocalChatState(ctx context.Context, infos []keybase1.BadgeConversationInfo) (res []keybase1.BadgeConversationInfo, smallTeamBadgeCount, bigTeamBadgeCount int) { 704 convIDs := make([]chat1.ConversationID, 0, len(infos)) 705 for _, info := range infos { 706 if !info.IsEmpty() { 707 convIDs = append(convIDs, chat1.ConversationID(info.ConvID.Bytes())) 708 } 709 } 710 711 outbox := storage.NewOutbox(s.G(), s.uid) 712 obrs, oerr := outbox.PullAllConversations(ctx, true /*includeErrors */, false /*remove*/) 713 if oerr != nil { 714 s.Debug(ctx, "ApplyLocalChatState: failed to get outbox: %v", oerr) 715 } 716 717 failedOutboxMap := make(map[chat1.ConvIDStr]int) 718 localUpdates := make(map[chat1.ConvIDStr]chat1.LocalMtimeUpdate) 719 s.Debug(ctx, "ApplyLocalChatState: looking through %d outbox items for badgable errors", len(obrs)) 720 for _, obr := range obrs { 721 if !(obr.IsBadgable() && obr.IsError()) { 722 s.Debug(ctx, "ApplyLocalChatState: skipping msgTyp: %v", obr.Msg.MessageType()) 723 continue 724 } 725 ctime := obr.Ctime 726 isBadgableError := obr.State.Error().Typ.IsBadgableError() 727 s.Debug(ctx, "ApplyLocalChatState: found erred outbox item ctime: %v, error: %v, messageType: %v, IsBadgableError: %v", 728 ctime.Time(), obr.State.Error(), obr.Msg.MessageType(), isBadgableError) 729 if !isBadgableError { 730 continue 731 } 732 convIDStr := obr.ConvID.ConvIDStr() 733 if update, ok := localUpdates[convIDStr]; ok { 734 if ctime.After(update.Mtime) { 735 localUpdates[convIDStr] = update 736 } 737 } else { 738 localUpdates[convIDStr] = chat1.LocalMtimeUpdate{ 739 ConvID: obr.ConvID, 740 Mtime: ctime, 741 } 742 } 743 convIDs = append(convIDs, obr.ConvID) 744 failedOutboxMap[convIDStr]++ 745 } 746 747 _, convs, err := s.createInbox().Read(ctx, s.uid, &chat1.GetInboxQuery{ 748 ConvIDs: convIDs, 749 }) 750 if err != nil { 751 s.Debug(ctx, "ApplyLocalChatState: failed to get convs: %v, charging forward", err) 752 } 753 // convID -> isRead 754 readConvMap := make(map[chat1.ConvIDStr]bool) 755 smallTeamConvMap := make(map[chat1.ConvIDStr]bool, len(convs)) 756 for _, conv := range convs { 757 if conv.IsLocallyRead() { 758 readConvMap[conv.ConvIDStr] = true 759 } 760 smallTeamConvMap[conv.ConvIDStr] = conv.GetTeamType() != chat1.TeamType_COMPLEX 761 } 762 763 updates := make([]chat1.LocalMtimeUpdate, 0, len(localUpdates)) 764 for _, update := range localUpdates { 765 updates = append(updates, update) 766 } 767 if err := s.createInbox().UpdateLocalMtime(ctx, s.uid, updates); err != nil { 768 s.Debug(ctx, "ApplyLocalChatState: unable to apply UpdateLocalMtime: %v", err) 769 } 770 771 res = make([]keybase1.BadgeConversationInfo, 0, len(infos)+len(failedOutboxMap)) 772 for _, info := range infos { 773 convIDStr := chat1.ConvIDStr(info.ConvID.String()) 774 // mark this conv as read 775 if readConvMap[convIDStr] { 776 info = makeBadgeConversationInfo(info.ConvID, 0) 777 s.Debug(ctx, "ApplyLocalChatState, marking as read %+v", info) 778 } 779 // badge qualifying failed outbox items 780 if failedCount, ok := failedOutboxMap[convIDStr]; ok { 781 newInfo := makeBadgeConversationInfo(info.ConvID, failedCount) 782 newInfo.BadgeCount += info.BadgeCount 783 newInfo.UnreadMessages += info.UnreadMessages 784 info = newInfo 785 delete(failedOutboxMap, convIDStr) 786 s.Debug(ctx, "ApplyLocalChatState, applying failed to existing info %+v", info) 787 } 788 if isSmallTeam, ok := smallTeamConvMap[convIDStr]; ok { 789 if isSmallTeam { 790 smallTeamBadgeCount += info.BadgeCount 791 } else { 792 bigTeamBadgeCount += info.BadgeCount 793 } 794 } 795 res = append(res, info) 796 } 797 798 // apply any new failed outbox items 799 for convIDStr, failedCount := range failedOutboxMap { 800 convID, err := chat1.MakeConvID(convIDStr.String()) 801 if err != nil { 802 s.Debug(ctx, "ApplyLocalChatState: Unable to make convID: %v", err) 803 continue 804 } 805 newInfo := makeBadgeConversationInfo(keybase1.ChatConversationID(convID), failedCount) 806 s.Debug(ctx, "ApplyLocalChatState, applying failed to new info %+v", newInfo) 807 res = append(res, newInfo) 808 } 809 return res, smallTeamBadgeCount, bigTeamBadgeCount 810 } 811 812 func (s *HybridInboxSource) Draft(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 813 text *string) (err error) { 814 defer s.maybeNuke(ctx, uid, &convID, &err) 815 _, err = s.createInbox().Draft(ctx, uid, convID, text) 816 if err != nil { 817 return err 818 } 819 return nil 820 } 821 822 func (s *HybridInboxSource) UpdateLocalMtime(ctx context.Context, uid gregor1.UID, updates []chat1.LocalMtimeUpdate) error { 823 if err := s.createInbox().UpdateLocalMtime(ctx, uid, updates); err != nil { 824 return err 825 } 826 return nil 827 } 828 829 func (s *HybridInboxSource) MergeLocalMetadata(ctx context.Context, uid gregor1.UID, convs []chat1.ConversationLocal) (err error) { 830 defer s.Trace(ctx, &err, "MergeLocalMetadata")() 831 defer s.maybeNuke(ctx, uid, nil, &err) 832 return s.createInbox().MergeLocalMetadata(ctx, uid, convs) 833 } 834 835 func (s *HybridInboxSource) NotifyUpdate(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) { 836 if err := s.createInbox().IncrementLocalConvVersion(ctx, uid, convID); err != nil { 837 s.Debug(ctx, "NotifyUpdate: unable to IncrementLocalConvVersion, err", err) 838 } 839 conv, err := s.getConvLocal(ctx, uid, convID) 840 if err != nil { 841 s.Debug(ctx, "NotifyUpdate: unable to getConvLocal, err", err) 842 } 843 var inboxUIItem *chat1.InboxUIItem 844 topicType := chat1.TopicType_NONE 845 if conv != nil { 846 inboxUIItem = PresentConversationLocalWithFetchRetry(ctx, s.G(), uid, *conv, 847 utils.PresentParticipantsModeSkip) 848 topicType = conv.GetTopicType() 849 } 850 s.G().ActivityNotifier.ConvUpdate(ctx, uid, convID, 851 topicType, inboxUIItem) 852 } 853 854 func (s *HybridInboxSource) IncrementLocalConvVersion(ctx context.Context, uid gregor1.UID, 855 convID chat1.ConversationID) (conv *chat1.ConversationLocal, err error) { 856 defer s.Trace(ctx, &err, "IncrementLocalConvVersion")() 857 defer s.maybeNuke(ctx, uid, &convID, &err) 858 if err := s.createInbox().IncrementLocalConvVersion(ctx, uid, convID); err != nil { 859 s.Debug(ctx, "IncrementLocalConvVersion: unable to IncrementLocalConvVersion, err", err) 860 } 861 return s.getConvLocal(ctx, uid, convID) 862 } 863 864 func (s *HybridInboxSource) MarkAsRead(ctx context.Context, convID chat1.ConversationID, 865 uid gregor1.UID, msgID *chat1.MessageID, forceUnread bool) (err error) { 866 defer s.Trace(ctx, &err, "MarkAsRead(%s,%d, %v)", convID, msgID, forceUnread)() 867 defer s.maybeNuke(ctx, uid, &convID, &err) 868 if !forceUnread { 869 // Check local copy to see if we have this convo, and have fully read 870 // it. If so, we skip the remote call unless 871 readRes, err := s.createInbox().GetConversation(ctx, uid, convID) 872 if err == nil && readRes.GetConvID().Eq(convID) && 873 readRes.Conv.ReaderInfo.ReadMsgid == readRes.Conv.ReaderInfo.MaxMsgid { 874 s.Debug(ctx, "MarkAsRead: conversation fully read: %s, not sending remote call", convID) 875 return nil 876 } 877 } 878 if msgID == nil { 879 conv, err := utils.GetUnverifiedConv(ctx, s.G(), uid, convID, types.InboxSourceDataSourceAll) 880 if err != nil { 881 return err 882 } 883 msgID = new(chat1.MessageID) 884 *msgID = conv.Conv.ReaderInfo.MaxMsgid 885 } 886 if err := s.createInbox().MarkLocalRead(ctx, uid, convID, *msgID); err != nil { 887 s.Debug(ctx, "MarkAsRead: failed to mark local read: %s", err) 888 } else { 889 if err := s.G().Badger.Send(ctx); err != nil { 890 return err 891 } 892 } 893 if err := s.readOutbox.PushRead(ctx, convID, *msgID, forceUnread); err != nil { 894 return err 895 } 896 s.flushMarkAsRead(ctx) 897 return nil 898 } 899 900 func (s *HybridInboxSource) fetchRemoteInbox(ctx context.Context, uid gregor1.UID, 901 query *chat1.GetInboxQuery) (res types.Inbox, err error) { 902 defer s.Trace(ctx, &err, "fetchRemoteInbox")() 903 904 // Insta fail if we are offline 905 if s.IsOffline(ctx) { 906 return types.Inbox{}, OfflineError{} 907 } 908 909 // We always want this on for fetches to fill the local inbox, otherwise we never get the 910 // full list for the conversations that come back 911 var rquery chat1.GetInboxQuery 912 if query == nil { 913 rquery = chat1.GetInboxQuery{ 914 ComputeActiveList: true, 915 } 916 } else { 917 rquery = *query 918 rquery.ComputeActiveList = true 919 } 920 rquery.SummarizeMaxMsgs = true // always summarize max msgs 921 922 ib, err := s.getChatInterface().GetInboxRemote(ctx, chat1.GetInboxRemoteArg{ 923 Query: s.setDefaultParticipantMode(&rquery), 924 }) 925 if err != nil { 926 return types.Inbox{}, err 927 } 928 929 var bgEnqueued int 930 // Limit the number of jobs we enqueue when on a limited data connection in 931 // mobile. 932 maxBgEnqueued := 10 933 if s.G().MobileNetState.State().IsLimited() { 934 maxBgEnqueued = 3 935 } 936 937 for _, conv := range ib.Inbox.Full().Conversations { 938 // Retention policy expunge 939 expunge := conv.GetExpunge() 940 if expunge != nil { 941 err := s.G().ConvSource.Expunge(ctx, utils.RemoteConv(conv), uid, *expunge) 942 if err != nil { 943 return types.Inbox{}, err 944 } 945 } 946 if query != nil && query.SkipBgLoads { 947 continue 948 } 949 // Queue all these convs up to be loaded by the background loader. Only 950 // load first maxBgEnqueued non KBFS convs, ACTIVE convs so we don't 951 // get the conv loader too backed up. 952 if conv.Metadata.MembersType != chat1.ConversationMembersType_KBFS && 953 (conv.HasMemberStatus(chat1.ConversationMemberStatus_ACTIVE) || 954 conv.HasMemberStatus(chat1.ConversationMemberStatus_PREVIEW)) && 955 bgEnqueued < maxBgEnqueued { 956 job := types.NewConvLoaderJob(conv.GetConvID(), &chat1.Pagination{Num: 50}, 957 types.ConvLoaderPriorityMedium, types.ConvLoaderGeneric, nil) 958 if err := s.G().ConvLoader.Queue(ctx, job); err != nil { 959 s.Debug(ctx, "fetchRemoteInbox: failed to queue conversation load: %s", err) 960 } 961 bgEnqueued++ 962 } 963 } 964 convs := utils.RemoteConvs(ib.Inbox.Full().Conversations) 965 convs = utils.ApplyInboxQuery(ctx, s.DebugLabeler, query, convs) 966 return types.Inbox{ 967 Version: ib.Inbox.Full().Vers, 968 ConvsUnverified: convs, 969 }, nil 970 } 971 972 func (s *HybridInboxSource) Read(ctx context.Context, uid gregor1.UID, 973 localizerTyp types.ConversationLocalizerTyp, dataSource types.InboxSourceDataSourceTyp, maxLocalize *int, 974 query *chat1.GetInboxLocalQuery) (inbox types.Inbox, localizeCb chan types.AsyncInboxResult, err error) { 975 defer s.Trace(ctx, &err, "Read")() 976 defer s.maybeNuke(ctx, uid, nil, &err) 977 978 // Read unverified inbox 979 rquery, tlfInfo, err := s.GetInboxQueryLocalToRemote(ctx, query) 980 if err != nil { 981 return inbox, localizeCb, err 982 } 983 inbox, err = s.ReadUnverified(ctx, uid, dataSource, rquery) 984 if err != nil { 985 return inbox, localizeCb, err 986 } 987 // we add an additional 1 here for the unverified payload which is also sent 988 // on this channel 989 localizeCb = make(chan types.AsyncInboxResult, len(inbox.ConvsUnverified)+1) 990 localizer := s.createConversationLocalizer(ctx, localizerTyp, localizeCb) 991 s.Debug(ctx, "Read: using localizer: %s on %d convs", localizer.Name(), len(inbox.ConvsUnverified)) 992 993 // Localize 994 inbox.Convs, err = localizer.Localize(ctx, uid, inbox, maxLocalize) 995 if err != nil { 996 return inbox, localizeCb, err 997 } 998 999 // Run post filters 1000 inbox.Convs, err = filterConvLocals(inbox.Convs, rquery, query, tlfInfo) 1001 if err != nil { 1002 return inbox, localizeCb, err 1003 } 1004 1005 // Write metadata to the inbox cache 1006 if err = s.createInbox().MergeLocalMetadata(ctx, uid, inbox.Convs); err != nil { 1007 // Don't abort the operation on this kind of error 1008 s.Debug(ctx, "Read: unable to write inbox local metadata: %s", err) 1009 } 1010 1011 return inbox, localizeCb, nil 1012 } 1013 1014 func (s *HybridInboxSource) ReadUnverified(ctx context.Context, uid gregor1.UID, 1015 dataSource types.InboxSourceDataSourceTyp, query *chat1.GetInboxQuery) (res types.Inbox, err error) { 1016 defer s.Trace(ctx, &err, "ReadUnverified")() 1017 defer s.maybeNuke(ctx, uid, nil, &err) 1018 1019 var cerr storage.Error 1020 inboxStore := s.createInbox() 1021 mergeInboxStore := false 1022 1023 // Try local storage (if enabled) 1024 switch dataSource { 1025 case types.InboxSourceDataSourceLocalOnly, types.InboxSourceDataSourceAll: 1026 var vers chat1.InboxVers 1027 var convs []types.RemoteConversation 1028 mergeInboxStore = true 1029 vers, convs, cerr = inboxStore.Read(ctx, uid, query) 1030 if cerr == nil { 1031 s.Debug(ctx, "ReadUnverified: hit local storage: uid: %s convs: %d", uid, len(convs)) 1032 res = types.Inbox{ 1033 Version: vers, 1034 ConvsUnverified: convs, 1035 } 1036 } else { 1037 if dataSource == types.InboxSourceDataSourceLocalOnly { 1038 s.Debug(ctx, "ReadUnverified: missed local storage, and in local only mode: %s", cerr) 1039 return res, cerr 1040 } 1041 } 1042 default: 1043 cerr = storage.MissError{} 1044 } 1045 1046 // If we hit an error reading from storage, then read from remote 1047 if cerr != nil { 1048 if _, ok := cerr.(storage.MissError); !ok { 1049 s.Debug(ctx, "ReadUnverified: error fetching inbox: %s", cerr.Error()) 1050 } else { 1051 s.Debug(ctx, "ReadUnverified: storage miss") 1052 } 1053 1054 // Go to the remote on miss 1055 res, err = s.fetchRemoteInbox(ctx, uid, query) 1056 if err != nil { 1057 return res, err 1058 } 1059 1060 // Write out to local storage only if we are using local data 1061 if mergeInboxStore { 1062 if cerr = inboxStore.Merge(ctx, uid, res.Version, utils.PluckConvs(res.ConvsUnverified), query); cerr != nil { 1063 s.Debug(ctx, "ReadUnverified: failed to write inbox to local storage: %s", cerr.Error()) 1064 } 1065 } 1066 } 1067 1068 return res, err 1069 } 1070 1071 type nameContainsQueryRes int 1072 1073 const ( 1074 nameContainsQueryNone nameContainsQueryRes = iota 1075 nameContainsQuerySimilar 1076 nameContainsQueryPrefix 1077 fullNameContainsQueryExact 1078 nameContainsQueryExact 1079 nameContainsQueryUnread 1080 nameContainsQueryBadged 1081 ) 1082 1083 type convSearchHit struct { 1084 conv types.RemoteConversation 1085 queryToks []string 1086 convToks []string 1087 nameToks []string 1088 hits []nameContainsQueryRes 1089 } 1090 1091 // weight contacts in the past week 1092 const ( 1093 lastActiveWeight = 50.0 1094 lastActiveMinHours = 24 // time in the last day yields max score 1095 lastActiveMaxHours = 7 * 24 // time greater than a week yields min score 1096 ) 1097 1098 func (h convSearchHit) score(emptyMode types.InboxSourceSearchEmptyMode) (score float64) { 1099 exactNames := 0 1100 for _, hit := range h.hits { 1101 switch hit { 1102 case nameContainsQueryExact: 1103 score += 20 1104 exactNames++ 1105 case fullNameContainsQueryExact: 1106 score += 50 1107 case nameContainsQueryPrefix: 1108 score += 10 1109 case nameContainsQuerySimilar: 1110 score += 3 1111 case nameContainsQueryUnread: 1112 score += 100 1113 case nameContainsQueryBadged: 1114 score += 200 1115 } 1116 } 1117 if len(h.queryToks) == len(h.convToks) && exactNames >= len(h.convToks) { 1118 score += 1000000 1119 } 1120 1121 var htime gregor1.Time 1122 switch emptyMode { 1123 case types.InboxSourceSearchEmptyModeAllBySendCtime: 1124 htime = utils.GetConvLastSendTime(h.conv) 1125 default: 1126 htime = utils.GetConvMtime(h.conv) 1127 } 1128 1129 lastActiveScore := opensearch.NormalizeLastActive(lastActiveMinHours, lastActiveMaxHours, keybase1.Time(htime)) 1130 score += lastActiveScore * lastActiveWeight 1131 return score 1132 } 1133 1134 func (h convSearchHit) less(o convSearchHit, emptyMode types.InboxSourceSearchEmptyMode) bool { 1135 hScore := h.score(emptyMode) 1136 oScore := o.score(emptyMode) 1137 if hScore < oScore { 1138 return true 1139 } else if hScore > oScore { 1140 return false 1141 } 1142 var htime, otime gregor1.Time 1143 switch emptyMode { 1144 case types.InboxSourceSearchEmptyModeAllBySendCtime: 1145 htime = utils.GetConvLastSendTime(h.conv) 1146 otime = utils.GetConvLastSendTime(o.conv) 1147 default: 1148 htime = utils.GetConvMtime(h.conv) 1149 otime = utils.GetConvMtime(o.conv) 1150 } 1151 return htime.Before(otime) 1152 } 1153 1154 func (h convSearchHit) valid() bool { 1155 return len(h.hits) > 0 1156 } 1157 1158 func (s *HybridInboxSource) fullNamesForSearch(ctx context.Context, conv types.RemoteConversation, 1159 convName, username string) (res []string) { 1160 switch conv.GetMembersType() { 1161 case chat1.ConversationMembersType_TEAM: 1162 return nil 1163 default: 1164 } 1165 if conv.LocalMetadata == nil { 1166 return nil 1167 } 1168 for index, name := range conv.LocalMetadata.FullNamesForSearch { 1169 if name == nil { 1170 continue 1171 } 1172 if index >= len(conv.LocalMetadata.WriterNames) { 1173 continue 1174 } 1175 if conv.LocalMetadata.WriterNames[index] == username && convName != username { 1176 continue 1177 } 1178 res = append(res, strings.Split(strings.ToLower(*name), " ")...) 1179 } 1180 return res 1181 } 1182 1183 func (s *HybridInboxSource) isConvSearchHit(ctx context.Context, conv types.RemoteConversation, 1184 queryToks []string, username string, emptyMode types.InboxSourceSearchEmptyMode) (res convSearchHit) { 1185 var convToks []string 1186 res.conv = conv 1187 res.queryToks = queryToks 1188 if len(queryToks) == 0 { 1189 switch emptyMode { 1190 case types.InboxSourceSearchEmptyModeUnread: 1191 if conv.Conv.IsUnread() { 1192 cqe := nameContainsQueryUnread 1193 if s.G().Badger.State().ConversationBadge(ctx, conv.GetConvID()) > 0 { 1194 cqe = nameContainsQueryBadged 1195 } 1196 res.hits = []nameContainsQueryRes{cqe} 1197 } 1198 default: 1199 res.hits = []nameContainsQueryRes{nameContainsQueryExact} 1200 } 1201 return res 1202 } 1203 convName := utils.SearchableRemoteConversationName(conv, username) 1204 switch conv.GetMembersType() { 1205 case chat1.ConversationMembersType_TEAM: 1206 convToks = []string{convName} 1207 default: 1208 convToks = strings.Split(convName, ",") 1209 } 1210 res.convToks = convToks 1211 res.nameToks = s.fullNamesForSearch(ctx, conv, convName, username) 1212 for _, queryTok := range queryToks { 1213 curHit := nameContainsQueryNone 1214 for i, convTok := range append(convToks, res.nameToks...) { 1215 if nameContainsQueryExact > curHit && convTok == queryTok { 1216 if i < len(res.convToks) { 1217 curHit = nameContainsQueryExact 1218 } else { // full name matches are slightly lower than name matches 1219 curHit = fullNameContainsQueryExact 1220 } 1221 } else if nameContainsQueryPrefix > curHit && strings.HasPrefix(convTok, queryTok) { 1222 curHit = nameContainsQueryPrefix 1223 } else if nameContainsQuerySimilar > curHit && strings.Contains(convTok, queryTok) { 1224 curHit = nameContainsQuerySimilar 1225 } 1226 } 1227 if curHit > nameContainsQueryNone { 1228 res.hits = append(res.hits, curHit) 1229 } 1230 } 1231 return res 1232 } 1233 1234 func (s *HybridInboxSource) Search(ctx context.Context, uid gregor1.UID, query string, limit int, 1235 emptyMode types.InboxSourceSearchEmptyMode) (res []types.RemoteConversation, err error) { 1236 defer s.Trace(ctx, &err, "Search")() 1237 defer s.maybeNuke(ctx, uid, nil, &err) 1238 username := s.G().GetEnv().GetUsernameForUID(keybase1.UID(uid.String())).String() 1239 ib := s.createInbox() 1240 _, convs, err := ib.ReadAll(ctx, uid, true) 1241 if err != nil { 1242 return res, err 1243 } 1244 // normalize the search query to lowercase 1245 query = strings.ToLower(query) 1246 var queryToks []string 1247 for _, t := range strings.FieldsFunc(query, func(r rune) bool { 1248 return r == ',' || r == ' ' 1249 }) { 1250 tok := strings.Trim(t, " ") 1251 if len(tok) > 0 { 1252 queryToks = append(queryToks, tok) 1253 } 1254 } 1255 var hits []convSearchHit 1256 for _, conv := range convs { 1257 if conv.Conv.GetTopicType() != chat1.TopicType_CHAT || 1258 utils.IsConvEmpty(conv.Conv) || conv.Conv.IsPublic() || 1259 !s.searchStatusMap[conv.Conv.Metadata.Status] || 1260 !s.searchMemberStatusMap[conv.Conv.ReaderInfo.Status] { 1261 continue 1262 } 1263 hit := s.isConvSearchHit(ctx, conv, queryToks, username, emptyMode) 1264 if !hit.valid() { 1265 continue 1266 } 1267 hits = append(hits, hit) 1268 } 1269 1270 sort.Slice(hits, func(i, j int) bool { 1271 return hits[j].less(hits[i], emptyMode) 1272 }) 1273 res = make([]types.RemoteConversation, len(hits)) 1274 for i, hit := range hits { 1275 res[i] = hit.conv 1276 } 1277 if limit > 0 && limit < len(res) { 1278 res = res[:limit] 1279 } 1280 return res, nil 1281 } 1282 1283 func (s *HybridInboxSource) IsTeam(ctx context.Context, uid gregor1.UID, item string) (res bool, err error) { 1284 defer s.Trace(ctx, &err, "IsTeam")() 1285 _, convs, err := s.createInbox().ReadAll(ctx, uid, true) 1286 if err != nil { 1287 return res, err 1288 } 1289 for _, conv := range convs { 1290 if conv.GetMembersType() == chat1.ConversationMembersType_TEAM && 1291 utils.GetRemoteConvTLFName(conv) == item { 1292 return true, nil 1293 } 1294 } 1295 return false, nil 1296 } 1297 1298 func (s *HybridInboxSource) handleInboxError(ctx context.Context, err error, uid gregor1.UID) (ferr error) { 1299 defer func() { 1300 if ferr != nil { 1301 // Only do this aggressive clear if the error we get is not some kind of network error 1302 _, isStorageAbort := ferr.(storage.AbortedError) 1303 if ferr != context.Canceled && !isStorageAbort && 1304 IsOfflineError(ferr) == OfflineErrorKindOnline { 1305 s.Debug(ctx, "handleInboxError: failed to recover from inbox error, clearing: %s", ferr) 1306 err := s.createInbox().Clear(ctx, uid) 1307 if err != nil { 1308 s.Debug(ctx, "handleInboxError: error clearing inbox: %+v", err) 1309 } 1310 } else { 1311 s.Debug(ctx, "handleInboxError: skipping inbox clear because of offline error: %s", ferr) 1312 } 1313 } 1314 s.G().UIInboxLoader.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "inbox error") 1315 }() 1316 1317 if _, ok := err.(storage.MissError); ok { 1318 return nil 1319 } 1320 if verr, ok := err.(storage.VersionMismatchError); ok { 1321 s.Debug(ctx, "handleInboxError: version mismatch, syncing and sending stale notifications: %s", 1322 verr.Error()) 1323 return s.G().Syncer.Sync(ctx, s.getChatInterface(), uid, nil) 1324 } 1325 return err 1326 } 1327 1328 func (s *HybridInboxSource) NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1329 conv chat1.Conversation) (err error) { 1330 defer s.Trace(ctx, &err, "NewConversation")() 1331 if cerr := s.createInbox().NewConversation(ctx, uid, vers, conv); cerr != nil { 1332 err = s.handleInboxError(ctx, cerr, uid) 1333 return err 1334 } 1335 1336 return nil 1337 } 1338 1339 func (s *HybridInboxSource) getConvLocal(ctx context.Context, uid gregor1.UID, 1340 convID chat1.ConversationID) (conv *chat1.ConversationLocal, err error) { 1341 // Read back affected conversation so we can send it to the frontend 1342 convs, err := s.getConvsLocal(ctx, uid, []chat1.ConversationID{convID}) 1343 if err != nil { 1344 return nil, err 1345 } 1346 if len(convs) == 0 { 1347 return nil, fmt.Errorf("unable to find conversation for new message: convID: %s", convID) 1348 } 1349 if len(convs) > 1 { 1350 return nil, fmt.Errorf("more than one conversation returned? convID: %s", convID) 1351 } 1352 return &convs[0], nil 1353 } 1354 1355 // Get convs. May return fewer or no conversations. 1356 func (s *HybridInboxSource) getConvsLocal(ctx context.Context, uid gregor1.UID, 1357 convIDs []chat1.ConversationID) ([]chat1.ConversationLocal, error) { 1358 // Read back affected conversation so we can send it to the frontend 1359 ib, _, err := s.Read(ctx, uid, types.ConversationLocalizerBlocking, types.InboxSourceDataSourceAll, nil, 1360 &chat1.GetInboxLocalQuery{ 1361 ConvIDs: convIDs, 1362 }) 1363 return ib.Convs, err 1364 } 1365 1366 func (s *HybridInboxSource) Sync(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convs []chat1.Conversation) (res types.InboxSyncRes, err error) { 1367 defer s.Trace(ctx, &err, "Sync")() 1368 return s.createInbox().Sync(ctx, uid, vers, convs) 1369 } 1370 1371 func (s *HybridInboxSource) NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1372 convID chat1.ConversationID, msg chat1.MessageBoxed, maxMsgs []chat1.MessageSummary) (conv *chat1.ConversationLocal, err error) { 1373 defer s.Trace(ctx, &err, "NewMessage")() 1374 if cerr := s.createInbox().NewMessage(ctx, uid, vers, convID, msg, maxMsgs); cerr != nil { 1375 err = s.handleInboxError(ctx, cerr, uid) 1376 return nil, err 1377 } 1378 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1379 s.Debug(ctx, "NewMessage: unable to load conversation: convID: %s err: %s", convID, err.Error()) 1380 return nil, nil 1381 } 1382 return conv, nil 1383 } 1384 1385 func (s *HybridInboxSource) ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1386 convID chat1.ConversationID, msgID chat1.MessageID) (conv *chat1.ConversationLocal, err error) { 1387 defer s.Trace(ctx, &err, "ReadMessage")() 1388 if cerr := s.createInbox().ReadMessage(ctx, uid, vers, convID, msgID); cerr != nil { 1389 err = s.handleInboxError(ctx, cerr, uid) 1390 return nil, err 1391 } 1392 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1393 s.Debug(ctx, "ReadMessage: unable to load conversation: convID: %s err: %s", convID, err.Error()) 1394 return nil, nil 1395 } 1396 return conv, nil 1397 1398 } 1399 1400 func (s *HybridInboxSource) SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1401 convID chat1.ConversationID, status chat1.ConversationStatus) (conv *chat1.ConversationLocal, err error) { 1402 defer s.Trace(ctx, &err, "SetStatus")() 1403 if cerr := s.createInbox().SetStatus(ctx, uid, vers, convID, status); cerr != nil { 1404 err = s.handleInboxError(ctx, cerr, uid) 1405 return nil, err 1406 } 1407 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1408 s.Debug(ctx, "SetStatus: unable to load conversation: convID: %s err: %s", convID, err.Error()) 1409 return nil, nil 1410 } 1411 return conv, nil 1412 } 1413 1414 func (s *HybridInboxSource) SetAppNotificationSettings(ctx context.Context, uid gregor1.UID, 1415 vers chat1.InboxVers, convID chat1.ConversationID, settings chat1.ConversationNotificationInfo) (conv *chat1.ConversationLocal, err error) { 1416 defer s.Trace(ctx, &err, "SetAppNotificationSettings")() 1417 ib := s.createInbox() 1418 if cerr := ib.SetAppNotificationSettings(ctx, uid, vers, convID, settings); cerr != nil { 1419 err = s.handleInboxError(ctx, cerr, uid) 1420 return nil, err 1421 } 1422 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1423 s.Debug(ctx, "SetAppNotificationSettings: unable to load conversation: convID: %s err: %s", 1424 convID, err.Error()) 1425 return nil, nil 1426 } 1427 return conv, nil 1428 } 1429 1430 func (s *HybridInboxSource) TeamTypeChanged(ctx context.Context, uid gregor1.UID, 1431 vers chat1.InboxVers, convID chat1.ConversationID, teamType chat1.TeamType) (conv *chat1.ConversationLocal, err error) { 1432 defer s.Trace(ctx, &err, "TeamTypeChanged")() 1433 1434 // Read the remote conversation so we can get the notification settings changes 1435 remoteConv, err := utils.GetUnverifiedConv(ctx, s.G(), uid, convID, 1436 types.InboxSourceDataSourceRemoteOnly) 1437 if err != nil { 1438 s.Debug(ctx, "TeamTypeChanged: failed to read team type conv: %s", err.Error()) 1439 return nil, err 1440 } 1441 ib := s.createInbox() 1442 if cerr := ib.TeamTypeChanged(ctx, uid, vers, convID, teamType, remoteConv.Conv.Notifications); cerr != nil { 1443 err = s.handleInboxError(ctx, cerr, uid) 1444 return nil, err 1445 } 1446 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1447 s.Debug(ctx, "TeamTypeChanged: unable to load conversation: convID: %s err: %s", 1448 convID, err.Error()) 1449 return nil, nil 1450 } 1451 return conv, nil 1452 } 1453 1454 func (s *HybridInboxSource) UpgradeKBFSToImpteam(ctx context.Context, uid gregor1.UID, 1455 vers chat1.InboxVers, convID chat1.ConversationID) (conv *chat1.ConversationLocal, err error) { 1456 defer s.Trace(ctx, &err, "UpgradeKBFSToImpteam")() 1457 1458 ib := s.createInbox() 1459 if cerr := ib.UpgradeKBFSToImpteam(ctx, uid, vers, convID); cerr != nil { 1460 err = s.handleInboxError(ctx, cerr, uid) 1461 return nil, err 1462 } 1463 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1464 s.Debug(ctx, "UpgradeKBFSToImpteam: unable to load conversation: convID: %s err: %s", 1465 convID, err.Error()) 1466 return nil, nil 1467 } 1468 return conv, nil 1469 } 1470 1471 func (s *HybridInboxSource) TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1472 convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) (convs []chat1.ConversationLocal, err error) { 1473 defer s.Trace(ctx, &err, "TlfFinalize")() 1474 1475 if cerr := s.createInbox().TlfFinalize(ctx, uid, vers, convIDs, finalizeInfo); cerr != nil { 1476 err = s.handleInboxError(ctx, cerr, uid) 1477 return convs, err 1478 } 1479 for _, convID := range convIDs { 1480 var conv *chat1.ConversationLocal 1481 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1482 s.Debug(ctx, "TlfFinalize: unable to get conversation: %s", convID) 1483 } 1484 if conv != nil { 1485 convs = append(convs, *conv) 1486 } 1487 } 1488 1489 // Notify rest of system about finalize 1490 s.notifyTlfFinalize(ctx, finalizeInfo.ResetUser) 1491 1492 return convs, nil 1493 } 1494 1495 func (s *HybridInboxSource) MembershipUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1496 joined []chat1.ConversationMember, removed []chat1.ConversationMember, resets []chat1.ConversationMember, 1497 previews []chat1.ConversationID, teamMemberRoleUpdate *chat1.TeamMemberRoleUpdate) (res types.MembershipUpdateRes, err error) { 1498 defer s.Trace(ctx, &err, "MembershipUpdate")() 1499 1500 // Separate into joins and removed on uid, and then on other users 1501 var userJoined []chat1.ConversationID 1502 for _, j := range joined { 1503 if j.Uid.Eq(uid) { 1504 userJoined = append(userJoined, j.ConvID) 1505 } else { 1506 res.OthersJoinedConvs = append(res.OthersJoinedConvs, j) 1507 } 1508 } 1509 // Append any previewed channels as well. We can do this since we just fetch all these conversations from 1510 // the server, and that will have the proper member status set. 1511 userJoined = append(userJoined, previews...) 1512 for _, r := range removed { 1513 if r.Uid.Eq(uid) { 1514 // Blow away conversation cache for any conversations we get removed from 1515 s.Debug(ctx, "MembershipUpdate: clear conv cache for removed conv: %s", r.ConvID) 1516 err := s.G().ConvSource.Clear(ctx, r.ConvID, uid, nil) 1517 if err != nil { 1518 s.Debug(ctx, "MembershipUpdate: error clearing conv source: %+v", err) 1519 } 1520 res.UserRemovedConvs = append(res.UserRemovedConvs, r) 1521 } else { 1522 res.OthersRemovedConvs = append(res.OthersRemovedConvs, r) 1523 } 1524 } 1525 1526 // Load the user joined conversations 1527 var userJoinedConvs []chat1.Conversation 1528 if len(userJoined) > 0 { 1529 var ibox types.Inbox 1530 ibox, _, err = s.Read(ctx, uid, types.ConversationLocalizerBlocking, 1531 types.InboxSourceDataSourceRemoteOnly, nil, 1532 &chat1.GetInboxLocalQuery{ 1533 ConvIDs: userJoined, 1534 }) 1535 if err != nil { 1536 s.Debug(ctx, "MembershipUpdate: failed to read joined convs: %s", err.Error()) 1537 return 1538 } 1539 1540 userJoinedConvs = utils.PluckConvs(ibox.ConvsUnverified) 1541 res.UserJoinedConvs = ibox.Convs 1542 } 1543 1544 for _, r := range resets { 1545 if r.Uid.Eq(uid) { 1546 res.UserResetConvs = append(res.UserResetConvs, r) 1547 } else { 1548 res.OthersResetConvs = append(res.OthersResetConvs, r) 1549 } 1550 } 1551 1552 ib := s.createInbox() 1553 roleUpdates, cerr := ib.MembershipUpdate(ctx, uid, vers, userJoinedConvs, res.UserRemovedConvs, 1554 res.OthersJoinedConvs, res.OthersRemovedConvs, res.UserResetConvs, 1555 res.OthersResetConvs, teamMemberRoleUpdate) 1556 if cerr != nil { 1557 err = s.handleInboxError(ctx, cerr, uid) 1558 return res, err 1559 } 1560 if len(roleUpdates) > 0 { 1561 convs, err := s.getConvsLocal(ctx, uid, roleUpdates) 1562 if err != nil { 1563 s.Debug(ctx, "MembershipUpdate: failed to read role update convs: %v", err) 1564 return res, err 1565 } 1566 res.RoleUpdates = convs 1567 } 1568 1569 return res, nil 1570 } 1571 1572 func (s *HybridInboxSource) ConversationsUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1573 convUpdates []chat1.ConversationUpdate) (err error) { 1574 defer s.Trace(ctx, &err, "ConversationUpdate")() 1575 1576 ib := s.createInbox() 1577 if cerr := ib.ConversationsUpdate(ctx, uid, vers, convUpdates); cerr != nil { 1578 err = s.handleInboxError(ctx, cerr, uid) 1579 return err 1580 } 1581 1582 return nil 1583 } 1584 1585 func (s *HybridInboxSource) Expunge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID, 1586 expunge chat1.Expunge, maxMsgs []chat1.MessageSummary) (*chat1.ConversationLocal, error) { 1587 return s.modConversation(ctx, "Expunge", uid, convID, func(ctx context.Context, ib *storage.Inbox) error { 1588 return ib.Expunge(ctx, uid, vers, convID, expunge, maxMsgs) 1589 }) 1590 } 1591 1592 func (s *HybridInboxSource) SetConvRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1593 convID chat1.ConversationID, policy chat1.RetentionPolicy) (res *chat1.ConversationLocal, err error) { 1594 return s.modConversation(ctx, "SetConvRetention", uid, convID, func(ctx context.Context, ib *storage.Inbox) error { 1595 return ib.SetConvRetention(ctx, uid, vers, convID, policy) 1596 }) 1597 } 1598 1599 func (s *HybridInboxSource) SetTeamRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1600 teamID keybase1.TeamID, policy chat1.RetentionPolicy) (convs []chat1.ConversationLocal, err error) { 1601 defer s.Trace(ctx, &err, "SetTeamRetention")() 1602 ib := s.createInbox() 1603 convIDs, cerr := ib.SetTeamRetention(ctx, uid, vers, teamID, policy) 1604 if cerr != nil { 1605 err = s.handleInboxError(ctx, cerr, uid) 1606 return nil, err 1607 } 1608 if convs, err = s.getConvsLocal(ctx, uid, convIDs); err != nil { 1609 s.Debug(ctx, "SetTeamRetention: unable to load conversations: convIDs: %v err: %s", 1610 convIDs, err.Error()) 1611 return nil, nil 1612 } 1613 return convs, nil 1614 } 1615 1616 func (s *HybridInboxSource) SetConvSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1617 convID chat1.ConversationID, convSettings *chat1.ConversationSettings) (res *chat1.ConversationLocal, err error) { 1618 return s.modConversation(ctx, "SetConvSettings", uid, convID, func(ctx context.Context, ib *storage.Inbox) error { 1619 return ib.SetConvSettings(ctx, uid, vers, convID, convSettings) 1620 }) 1621 } 1622 1623 func (s *HybridInboxSource) SubteamRename(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1624 convIDs []chat1.ConversationID) (convs []chat1.ConversationLocal, err error) { 1625 defer s.Trace(ctx, &err, "SubteamRename")() 1626 ib := s.createInbox() 1627 if cerr := ib.SubteamRename(ctx, uid, vers, convIDs); cerr != nil { 1628 err = s.handleInboxError(ctx, cerr, uid) 1629 return nil, err 1630 } 1631 if convs, err = s.getConvsLocal(ctx, uid, convIDs); err != nil { 1632 s.Debug(ctx, "SubteamRename: unable to load conversations: convIDs: %v err: %s", 1633 convIDs, err.Error()) 1634 return nil, nil 1635 } 1636 return convs, nil 1637 } 1638 1639 func (s *HybridInboxSource) UpdateInboxVersion(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers) (err error) { 1640 defer s.Trace(ctx, &err, "UpdateInboxVersion")() 1641 return s.createInbox().UpdateInboxVersion(ctx, uid, vers) 1642 } 1643 1644 func (s *HybridInboxSource) modConversation(ctx context.Context, debugLabel string, uid gregor1.UID, convID chat1.ConversationID, 1645 mod func(context.Context, *storage.Inbox) error) ( 1646 conv *chat1.ConversationLocal, err error) { 1647 defer s.Trace(ctx, &err, debugLabel)() 1648 ib := s.createInbox() 1649 if cerr := mod(ctx, ib); cerr != nil { 1650 err = s.handleInboxError(ctx, cerr, uid) 1651 return nil, err 1652 } 1653 if conv, err = s.getConvLocal(ctx, uid, convID); err != nil { 1654 s.Debug(ctx, "%v: unable to load conversation: convID: %s err: %v", 1655 debugLabel, convID, err) 1656 return nil, nil 1657 } 1658 return conv, nil 1659 } 1660 1661 func (s *HybridInboxSource) TeamBotSettingsForConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) ( 1662 teambotSettings map[keybase1.UID]keybase1.TeamBotSettings, err error) { 1663 defer s.Trace(ctx, &err, "TeamBotSettingsForConv")() 1664 rConv, err := s.createInbox().GetConversation(ctx, uid, convID) 1665 if err != nil { 1666 return nil, err 1667 } 1668 1669 metadata := rConv.Conv.Metadata 1670 public := metadata.Visibility == keybase1.TLFVisibility_PUBLIC 1671 tlfID := metadata.IdTriple.Tlfid 1672 infoSource := CreateNameInfoSource(ctx, s.G(), metadata.MembersType) 1673 var tlfName string 1674 if rConv.LocalMetadata == nil { 1675 info, err := infoSource.LookupName(ctx, tlfID, public, "") 1676 if err != nil { 1677 return nil, err 1678 } 1679 tlfName = info.CanonicalName 1680 } else { 1681 tlfName = rConv.LocalMetadata.Name 1682 } 1683 1684 teamBotSettings, err := infoSource.TeamBotSettings(ctx, tlfName, tlfID, metadata.MembersType, public) 1685 if err != nil { 1686 return nil, err 1687 } 1688 res := make(map[keybase1.UID]keybase1.TeamBotSettings) 1689 for uv, botSettings := range teamBotSettings { 1690 res[uv.Uid] = botSettings 1691 } 1692 return res, nil 1693 } 1694 1695 func (s *HybridInboxSource) RemoteSetConversationStatus(ctx context.Context, uid gregor1.UID, 1696 convID chat1.ConversationID, status chat1.ConversationStatus) (err error) { 1697 defer s.maybeNuke(ctx, uid, &convID, &err) 1698 if err := s.baseInboxSource.RemoteSetConversationStatus(ctx, uid, convID, status); err != nil { 1699 return err 1700 } 1701 return s.createInbox().SetStatus(ctx, uid, 0, convID, status) 1702 } 1703 1704 func (s *HybridInboxSource) RemoteDeleteConversation(ctx context.Context, uid gregor1.UID, 1705 convID chat1.ConversationID) (err error) { 1706 defer s.maybeNuke(ctx, uid, &convID, &err) 1707 if err := s.baseInboxSource.RemoteDeleteConversation(ctx, uid, convID); err != nil { 1708 return err 1709 } 1710 return s.createInbox().ConversationsUpdate(ctx, uid, 0, []chat1.ConversationUpdate{{ 1711 ConvID: convID, 1712 Existence: chat1.ConversationExistence_DELETED, 1713 }}) 1714 } 1715 1716 func (s *HybridInboxSource) Localize(ctx context.Context, uid gregor1.UID, convs []types.RemoteConversation, 1717 localizerTyp types.ConversationLocalizerTyp) (res []chat1.ConversationLocal, localizeCb chan types.AsyncInboxResult, err error) { 1718 defer s.maybeNuke(ctx, uid, nil, &err) 1719 return s.baseInboxSource.Localize(ctx, uid, convs, localizerTyp) 1720 } 1721 1722 func NewInboxSource(g *globals.Context, typ string, ri func() chat1.RemoteInterface) types.InboxSource { 1723 switch typ { 1724 case "hybrid": 1725 return NewHybridInboxSource(g, ri) 1726 default: 1727 return NewRemoteInboxSource(g, ri) 1728 } 1729 }