github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/storage/inbox.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/hex" 7 "fmt" 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 "golang.org/x/net/context" 18 ) 19 20 const inboxVersion = 32 21 22 type InboxFlushMode int 23 24 const ( 25 InboxFlushModeActive InboxFlushMode = iota 26 InboxFlushModeDelegate 27 ) 28 29 type queryHash []byte 30 31 func (q queryHash) Empty() bool { 32 return len(q) == 0 33 } 34 35 func (q queryHash) String() string { 36 return hex.EncodeToString(q) 37 } 38 39 func (q queryHash) Eq(r queryHash) bool { 40 return bytes.Equal(q, r) 41 } 42 43 type inboxDiskQuery struct { 44 QueryHash queryHash `codec:"Q"` 45 Pagination *chat1.Pagination `codec:"P"` 46 } 47 48 func (q inboxDiskQuery) queryMatch(other inboxDiskQuery) bool { 49 if q.QueryHash.Empty() && other.QueryHash.Empty() { 50 return true 51 } 52 if !q.QueryHash.Empty() && !other.QueryHash.Empty() { 53 return q.QueryHash.Eq(other.QueryHash) 54 } 55 return false 56 } 57 58 func (q inboxDiskQuery) match(other inboxDiskQuery) bool { 59 return q.queryMatch(other) && q.Pagination.Eq(other.Pagination) 60 } 61 62 type inboxDiskIndex struct { 63 ConversationIDs []chat1.ConversationID `codec:"C"` 64 Queries []inboxDiskQuery `codec:"Q"` 65 } 66 67 func (i inboxDiskIndex) DeepCopy() (res inboxDiskIndex) { 68 res.ConversationIDs = make([]chat1.ConversationID, len(i.ConversationIDs)) 69 res.Queries = make([]inboxDiskQuery, len(i.Queries)) 70 copy(res.ConversationIDs, i.ConversationIDs) 71 copy(res.Queries, i.Queries) 72 return res 73 } 74 75 func (i *inboxDiskIndex) mergeConvs(convIDs []chat1.ConversationID) { 76 m := make(map[string]chat1.ConversationID, len(convIDs)) 77 for _, convID := range convIDs { 78 m[convID.String()] = convID 79 } 80 for _, convID := range i.ConversationIDs { 81 delete(m, convID.String()) 82 } 83 for _, convID := range m { 84 i.ConversationIDs = append(i.ConversationIDs, convID) 85 } 86 } 87 88 func (i *inboxDiskIndex) merge(convIDs []chat1.ConversationID, hash queryHash) { 89 i.mergeConvs(convIDs) 90 queryExists := false 91 qp := inboxDiskQuery{QueryHash: hash} 92 for _, q := range i.Queries { 93 if q.queryMatch(qp) { 94 queryExists = true 95 break 96 } 97 } 98 if !queryExists { 99 i.Queries = append(i.Queries, qp) 100 } 101 } 102 103 type inboxDiskVersions struct { 104 Version int `codec:"V"` 105 ServerVersion int `codec:"S"` 106 InboxVersion chat1.InboxVers `codec:"I"` 107 } 108 109 type InboxLayoutChangedNotifier interface { 110 UpdateLayout(ctx context.Context, reselectMode chat1.InboxLayoutReselectMode, reason string) 111 UpdateLayoutFromNewMessage(ctx context.Context, conv types.RemoteConversation) 112 UpdateLayoutFromSubteamRename(ctx context.Context, convs []types.RemoteConversation) 113 } 114 115 type dummyInboxLayoutChangedNotifier struct{} 116 117 func (d dummyInboxLayoutChangedNotifier) UpdateLayout(ctx context.Context, 118 reselectMode chat1.InboxLayoutReselectMode, reason string) { 119 } 120 121 func (d dummyInboxLayoutChangedNotifier) UpdateLayoutFromNewMessage(ctx context.Context, 122 conv types.RemoteConversation) { 123 } 124 125 func (d dummyInboxLayoutChangedNotifier) UpdateLayoutFromSubteamRename(ctx context.Context, 126 convs []types.RemoteConversation) { 127 } 128 129 func LayoutChangedNotifier(notifier InboxLayoutChangedNotifier) func(*Inbox) { 130 return func(i *Inbox) { 131 i.SetInboxLayoutChangedNotifier(notifier) 132 } 133 } 134 135 type Inbox struct { 136 globals.Contextified 137 *baseBox 138 utils.DebugLabeler 139 140 layoutNotifier InboxLayoutChangedNotifier 141 } 142 143 func NewInbox(g *globals.Context, config ...func(*Inbox)) *Inbox { 144 i := &Inbox{ 145 Contextified: globals.NewContextified(g), 146 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Inbox", false), 147 baseBox: newBaseBox(g), 148 layoutNotifier: dummyInboxLayoutChangedNotifier{}, 149 } 150 for _, c := range config { 151 c(i) 152 } 153 return i 154 } 155 156 func (i *Inbox) SetInboxLayoutChangedNotifier(notifier InboxLayoutChangedNotifier) { 157 i.layoutNotifier = notifier 158 } 159 160 func (i *Inbox) dbVersionsKey(uid gregor1.UID) libkb.DbKey { 161 return libkb.DbKey{ 162 Typ: libkb.DBChatInbox, 163 Key: uid.String(), 164 } 165 } 166 167 func (i *Inbox) dbIndexKey(uid gregor1.UID) libkb.DbKey { 168 return libkb.DbKey{ 169 Typ: libkb.DBChatInboxIndex, 170 Key: uid.String(), 171 } 172 } 173 174 func (i *Inbox) dbConvKey(uid gregor1.UID, convID chat1.ConversationID) libkb.DbKey { 175 return libkb.DbKey{ 176 Typ: libkb.DBChatInboxConvs, 177 Key: uid.String() + convID.DbShortFormString(), 178 } 179 } 180 181 func (i *Inbox) maybeNuke(ctx context.Context, ef func() Error, uid gregor1.UID) { 182 err := ef() 183 if err != nil && err.ShouldClear() { 184 i.Debug(ctx, "maybeNuke: nuking on err: %v", err) 185 if ierr := i.clearLocked(ctx, uid); ierr != nil { 186 i.Debug(ctx, "maybeNuke: unable to clear box on error! err: %s", ierr) 187 } 188 } 189 } 190 191 func (i *Inbox) readDiskVersions(ctx context.Context, uid gregor1.UID, useInMemory bool) (inboxDiskVersions, Error) { 192 var ibox inboxDiskVersions 193 // Check context for an aborted request 194 if err := isAbortedRequest(ctx); err != nil { 195 return ibox, err 196 } 197 // Check in memory cache first 198 if memibox := inboxMemCache.GetVersions(uid); useInMemory && memibox != nil { 199 i.Debug(ctx, "readDiskVersions: hit in memory cache") 200 ibox = *memibox 201 } else { 202 found, err := i.readDiskBox(ctx, i.dbVersionsKey(uid), &ibox) 203 if err != nil { 204 if _, ok := err.(libkb.LoginRequiredError); ok { 205 return ibox, MiscError{Msg: err.Error()} 206 } 207 return ibox, NewInternalError(ctx, i.DebugLabeler, 208 "failed to read inbox: uid: %d err: %s", uid, err) 209 } 210 if !found { 211 return ibox, MissError{} 212 } 213 if useInMemory { 214 inboxMemCache.PutVersions(uid, &ibox) 215 } 216 } 217 // Check on disk server version against known server version 218 if _, err := i.G().ServerCacheVersions.MatchInbox(ctx, ibox.ServerVersion); err != nil { 219 i.Debug(ctx, "readDiskVersions: server version match error, clearing: %s", err) 220 if cerr := i.clearLocked(ctx, uid); cerr != nil { 221 i.Debug(ctx, "readDiskVersions: failed to clear after server mismatch: %s", cerr) 222 } 223 return ibox, MissError{} 224 } 225 // Check on disk version against configured 226 if ibox.Version != inboxVersion { 227 i.Debug(ctx, 228 "readDiskVersions: on disk version not equal to program version, clearing: disk :%d program: %d", 229 ibox.Version, inboxVersion) 230 if cerr := i.clearLocked(ctx, uid); cerr != nil { 231 i.Debug(ctx, "readDiskVersions: failed to clear after inbox mismatch: %s", cerr) 232 } 233 return ibox, MissError{} 234 } 235 236 i.Debug(ctx, "readDiskVersions: version: %d disk version: %d server version: %d", 237 ibox.InboxVersion, ibox.Version, ibox.ServerVersion) 238 239 return ibox, nil 240 } 241 242 func (i *Inbox) writeDiskVersions(ctx context.Context, uid gregor1.UID, ibox inboxDiskVersions) Error { 243 // Get latest server version 244 vers, err := i.G().ServerCacheVersions.Fetch(ctx) 245 if err != nil { 246 return NewInternalError(ctx, i.DebugLabeler, "failed to fetch server versions: %s", err) 247 } 248 ibox.ServerVersion = vers.InboxVers 249 ibox.Version = inboxVersion 250 i.Debug(ctx, "writeDiskVersions: uid: %s version: %d disk version: %d server version: %d", 251 uid, ibox.InboxVersion, ibox.Version, ibox.ServerVersion) 252 inboxMemCache.PutVersions(uid, &ibox) 253 if err := i.writeDiskBox(ctx, i.dbVersionsKey(uid), ibox); err != nil { 254 return NewInternalError(ctx, i.DebugLabeler, "failed to write inbox versions: %s", err) 255 } 256 return nil 257 } 258 259 func (i *Inbox) readDiskIndex(ctx context.Context, uid gregor1.UID, useInMemory bool) (inboxDiskIndex, Error) { 260 var ibox inboxDiskIndex 261 // Check context for an aborted request 262 if err := isAbortedRequest(ctx); err != nil { 263 return ibox, err 264 } 265 // Check in memory cache first 266 if memibox := inboxMemCache.GetIndex(uid); useInMemory && memibox != nil { 267 i.Debug(ctx, "readDiskIndex: hit in memory cache") 268 ibox = *memibox 269 } else { 270 found, err := i.readDiskBox(ctx, i.dbIndexKey(uid), &ibox) 271 if err != nil { 272 if _, ok := err.(libkb.LoginRequiredError); ok { 273 return ibox, MiscError{Msg: err.Error()} 274 } 275 return ibox, NewInternalError(ctx, i.DebugLabeler, 276 "failed to read inbox: uid: %d err: %s", uid, err) 277 } 278 if !found { 279 return ibox, MissError{} 280 } 281 if useInMemory { 282 inboxMemCache.PutIndex(uid, &ibox) 283 } 284 } 285 i.Debug(ctx, "readDiskIndex: convs: %d queries: %d", len(ibox.ConversationIDs), len(ibox.Queries)) 286 return ibox, nil 287 } 288 289 func (i *Inbox) writeDiskIndex(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex) Error { 290 i.Debug(ctx, "writeDiskIndex: convs: %d queries: %d", len(ibox.ConversationIDs), len(ibox.Queries)) 291 inboxMemCache.PutIndex(uid, &ibox) 292 if err := i.writeDiskBox(ctx, i.dbIndexKey(uid), ibox); err != nil { 293 return NewInternalError(ctx, i.DebugLabeler, "failed to write inbox index: %s", err) 294 } 295 return nil 296 } 297 298 func (i *Inbox) readConvs(ctx context.Context, uid gregor1.UID, convIDs []chat1.ConversationID) (res []types.RemoteConversation, err Error) { 299 res = make([]types.RemoteConversation, 0, len(convIDs)) 300 memHits := make(map[chat1.ConvIDStr]bool, len(convIDs)) 301 for _, convID := range convIDs { 302 if conv := inboxMemCache.GetConv(uid, convID); conv != nil { 303 res = append(res, *conv) 304 memHits[convID.ConvIDStr()] = true 305 } 306 } 307 if len(memHits) == len(convIDs) { 308 return res, nil 309 } 310 dbReads := 0 311 defer func() { 312 i.Debug(ctx, "readConvs: read %d convs from db", dbReads) 313 }() 314 for _, convID := range convIDs { 315 if memHits[convID.ConvIDStr()] { 316 continue 317 } 318 var conv types.RemoteConversation 319 dbReads++ 320 found, err := i.readDiskBox(ctx, i.dbConvKey(uid, convID), &conv) 321 if err != nil { 322 if _, ok := err.(libkb.LoginRequiredError); ok { 323 return res, MiscError{Msg: err.Error()} 324 } 325 return res, NewInternalError(ctx, i.DebugLabeler, 326 "failed to read inbox: uid: %d err: %s", uid, err) 327 } 328 if !found { 329 return res, MissError{} 330 } 331 inboxMemCache.PutConv(uid, conv) 332 res = append(res, conv) 333 } 334 return res, nil 335 } 336 337 func (i *Inbox) readConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, err Error) { 338 convs, err := i.readConvs(ctx, uid, []chat1.ConversationID{convID}) 339 if err != nil { 340 return res, err 341 } 342 if len(convs) == 0 { 343 return res, MissError{} 344 } 345 return convs[0], nil 346 } 347 348 func (i *Inbox) writeConvs(ctx context.Context, uid gregor1.UID, convs []types.RemoteConversation, 349 withVersionCheck bool) Error { 350 i.summarizeConvs(convs) 351 for _, conv := range convs { 352 if withVersionCheck { 353 existing, err := i.readConv(ctx, uid, conv.GetConvID()) 354 if err == nil && existing.GetVersion() >= conv.GetVersion() { 355 i.Debug(ctx, "writeConvs: skipping write because of newer stored version: convID: %s old: %d new: %d", 356 conv.ConvIDStr, existing.GetVersion(), conv.GetVersion()) 357 continue 358 } 359 } 360 i.Debug(ctx, "writeConvs: writing conv: %s", conv.ConvIDStr) 361 inboxMemCache.PutConv(uid, conv) 362 if err := i.writeDiskBox(ctx, i.dbConvKey(uid, conv.GetConvID()), conv); err != nil { 363 return NewInternalError(ctx, i.DebugLabeler, "failed to write conv: %s err: %s", conv.ConvIDStr, 364 err) 365 } 366 } 367 return nil 368 } 369 370 func (i *Inbox) writeConv(ctx context.Context, uid gregor1.UID, conv types.RemoteConversation, 371 withVersionCheck bool) Error { 372 return i.writeConvs(ctx, uid, []types.RemoteConversation{conv}, withVersionCheck) 373 } 374 375 type ByDatabaseOrder []types.RemoteConversation 376 377 func (a ByDatabaseOrder) Len() int { return len(a) } 378 func (a ByDatabaseOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 379 func (a ByDatabaseOrder) Less(i, j int) bool { 380 return utils.DBConvLess(a[i], a[j]) 381 } 382 383 func (i *Inbox) summarizeConv(rc *types.RemoteConversation) { 384 if len(rc.Conv.MaxMsgs) == 0 { 385 // early out here since we don't do anything if this is empty 386 return 387 } 388 389 summaries := make(map[chat1.MessageType]chat1.MessageSummary) 390 // Collect the existing summaries 391 for _, m := range rc.Conv.MaxMsgSummaries { 392 summaries[m.GetMessageType()] = m 393 } 394 395 // Collect the locally-grown summaries 396 for _, m := range rc.Conv.MaxMsgs { 397 summaries[m.GetMessageType()] = m.Summary() 398 } 399 400 // Insert all the summaries 401 rc.Conv.MaxMsgs = nil 402 rc.Conv.MaxMsgSummaries = nil 403 for _, m := range summaries { 404 rc.Conv.MaxMsgSummaries = append(rc.Conv.MaxMsgSummaries, m) 405 } 406 } 407 408 func (i *Inbox) summarizeConvs(convs []types.RemoteConversation) { 409 for index := range convs { 410 i.summarizeConv(&convs[index]) 411 } 412 } 413 414 func (i *Inbox) hashQuery(ctx context.Context, query *chat1.GetInboxQuery) (queryHash, Error) { 415 if query == nil { 416 return nil, nil 417 } 418 419 dat, err := encode(*query) 420 if err != nil { 421 return nil, NewInternalError(ctx, i.DebugLabeler, "failed to encode query: %s", err.Error()) 422 } 423 424 hasher := sha1.New() 425 _, err = hasher.Write(dat) 426 if err != nil { 427 return nil, NewInternalError(ctx, i.DebugLabeler, "failed to write query: %s", err.Error()) 428 } 429 return hasher.Sum(nil), nil 430 } 431 432 func (i *Inbox) readDiskVersionsIndexMissOk(ctx context.Context, uid gregor1.UID, useInMemory bool) (vers inboxDiskVersions, index inboxDiskIndex, err Error) { 433 if vers, err = i.readDiskVersions(ctx, uid, true); err != nil { 434 if _, ok := err.(MissError); !ok { 435 return vers, index, err 436 } 437 } 438 if index, err = i.readDiskIndex(ctx, uid, true); err != nil { 439 if _, ok := err.(MissError); !ok { 440 return vers, index, err 441 } 442 } 443 return vers, index, nil 444 } 445 446 func (i *Inbox) castInternalError(ierr Error) error { 447 err, ok := ierr.(error) 448 if ok { 449 return err 450 } 451 return nil 452 } 453 454 func (i *Inbox) MergeLocalMetadata(ctx context.Context, uid gregor1.UID, convs []chat1.ConversationLocal) (err Error) { 455 var ierr error 456 defer i.Trace(ctx, &ierr, "MergeLocalMetadata")() 457 defer func() { ierr = i.castInternalError(err) }() 458 locks.Inbox.Lock() 459 defer locks.Inbox.Unlock() 460 for _, convLocal := range convs { 461 conv, err := i.readConv(ctx, uid, convLocal.GetConvID()) 462 if err != nil { 463 i.Debug(ctx, "MergeLocalMetadata: skipping metadata for %s: err: %s", convLocal.GetConvID(), 464 err) 465 continue 466 } 467 // Don't write this out for error convos 468 if convLocal.Error != nil || convLocal.GetTopicType() != chat1.TopicType_CHAT { 469 continue 470 } 471 topicName := convLocal.Info.TopicName 472 snippetDecoration, snippet, _ := utils.GetConvSnippet(ctx, i.G(), uid, convLocal, 473 i.G().GetEnv().GetUsername().String()) 474 rcm := &types.RemoteConversationMetadata{ 475 Name: convLocal.Info.TlfName, 476 TopicName: topicName, 477 Headline: convLocal.Info.Headline, 478 HeadlineEmojis: convLocal.Info.HeadlineEmojis, 479 Snippet: snippet, 480 SnippetDecoration: snippetDecoration, 481 } 482 switch convLocal.GetMembersType() { 483 case chat1.ConversationMembersType_TEAM: 484 default: 485 rcm.WriterNames = convLocal.AllNames() 486 rcm.FullNamesForSearch = convLocal.FullNamesForSearch() 487 rcm.ResetParticipants = convLocal.Info.ResetNames 488 } 489 conv.LocalMetadata = rcm 490 if err := i.writeConv(ctx, uid, conv, false); err != nil { 491 return err 492 } 493 } 494 return nil 495 } 496 497 // Merge add/updates conversations into the inbox. If a given conversation is either missing 498 // from the inbox, or is of greater version than what is currently stored, we write it down. Otherwise, 499 // we ignore it. If the inbox is currently blank, then we write down the given inbox version. 500 func (i *Inbox) Merge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 501 convsIn []chat1.Conversation, query *chat1.GetInboxQuery) (err Error) { 502 var ierr error 503 defer i.Trace(ctx, &ierr, "Merge")() 504 defer func() { ierr = i.castInternalError(err) }() 505 locks.Inbox.Lock() 506 defer locks.Inbox.Unlock() 507 defer i.maybeNuke(ctx, func() Error { return err }, uid) 508 509 i.Debug(ctx, "Merge: vers: %d convs: %d", vers, len(convsIn)) 510 if len(convsIn) == 1 { 511 i.Debug(ctx, "Merge: single conversation: %s", convsIn[0].GetConvID()) 512 } 513 convIDs := make([]chat1.ConversationID, 0, len(convsIn)) 514 for _, conv := range convsIn { 515 convIDs = append(convIDs, conv.GetConvID()) 516 } 517 convs := make([]chat1.Conversation, len(convsIn)) 518 copy(convs, convsIn) 519 520 iboxVers, iboxIndex, err := i.readDiskVersionsIndexMissOk(ctx, uid, true) 521 if err != nil { 522 return err 523 } 524 525 // write all the convs out 526 if err := i.writeConvs(ctx, uid, utils.RemoteConvs(convs), true); err != nil { 527 return err 528 } 529 530 // update index 531 hquery, err := i.hashQuery(ctx, query) 532 if err != nil { 533 return err 534 } 535 i.Debug(ctx, "Merge: query hash: %s", hquery) 536 iboxIndex.merge(convIDs, hquery) 537 if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil { 538 return err 539 } 540 541 // updat eversion info 542 if iboxVers.InboxVersion != 0 { 543 vers = iboxVers.InboxVersion 544 } else { 545 i.Debug(ctx, "Merge: using given version: %d", vers) 546 } 547 i.Debug(ctx, "Merge: merging inbox: vers: %d convs: %d", vers, len(iboxIndex.ConversationIDs)) 548 // Write out new inbox 549 return i.writeDiskVersions(ctx, uid, inboxDiskVersions{ 550 Version: inboxVersion, 551 InboxVersion: vers, 552 }) 553 } 554 555 func (i *Inbox) queryNameExists(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex, 556 tlfID chat1.TLFID, membersType chat1.ConversationMembersType, topicName string, 557 topicType chat1.TopicType) bool { 558 convs, err := i.readConvs(ctx, uid, ibox.ConversationIDs) 559 if err != nil { 560 i.Debug(ctx, "queryNameExists: unexpected miss on index conv read: %s", err) 561 return false 562 } 563 for _, conv := range convs { 564 if conv.Conv.Metadata.IdTriple.Tlfid.Eq(tlfID) && conv.GetMembersType() == membersType && 565 conv.GetTopicName() == topicName && conv.GetTopicType() == topicType { 566 return true 567 } 568 } 569 return false 570 } 571 572 func (i *Inbox) queryExists(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex, 573 query *chat1.GetInboxQuery) bool { 574 575 // Check for a name query that is after a single conversation 576 if query != nil && query.TlfID != nil && query.TopicType != nil && query.TopicName != nil && 577 len(query.MembersTypes) == 1 { 578 if i.queryNameExists(ctx, uid, ibox, *query.TlfID, query.MembersTypes[0], *query.TopicName, 579 *query.TopicType) { 580 i.Debug(ctx, "Read: queryExists: single name query hit") 581 return true 582 } 583 } 584 585 // Normally a query that has not been seen before will return an error. 586 // With AllowUnseenQuery, an unfamiliar query is accepted. 587 if query != nil && query.AllowUnseenQuery { 588 return true 589 } 590 591 hquery, err := i.hashQuery(ctx, query) 592 if err != nil { 593 i.Debug(ctx, "Read: queryExists: error hashing query: %s", err) 594 return false 595 } 596 i.Debug(ctx, "Read: queryExists: query hash: %s", hquery) 597 598 qp := inboxDiskQuery{QueryHash: hquery} 599 for _, q := range ibox.Queries { 600 if q.match(qp) { 601 return true 602 } 603 } 604 return false 605 } 606 607 func (i *Inbox) ReadAll(ctx context.Context, uid gregor1.UID, useInMemory bool) (vers chat1.InboxVers, res []types.RemoteConversation, err Error) { 608 var ierr error 609 defer i.Trace(ctx, &ierr, "ReadAll")() 610 defer func() { ierr = i.castInternalError(err) }() 611 defer i.maybeNuke(ctx, func() Error { return err }, uid) 612 locks.Inbox.Lock() 613 defer locks.Inbox.Unlock() 614 615 iboxIndex, err := i.readDiskIndex(ctx, uid, useInMemory) 616 if err != nil { 617 if _, ok := err.(MissError); ok { 618 i.Debug(ctx, "Read: miss: no inbox index found") 619 } 620 return 0, nil, err 621 } 622 iboxVers, err := i.readDiskVersions(ctx, uid, useInMemory) 623 if err != nil { 624 if _, ok := err.(MissError); ok { 625 i.Debug(ctx, "Read: miss: no inbox version found") 626 } 627 return 0, nil, err 628 } 629 convs, err := i.readConvs(ctx, uid, iboxIndex.ConversationIDs) 630 if err != nil { 631 i.Debug(ctx, "ReadAll: unexpected miss on index conv read: %s", err) 632 return 0, nil, err 633 } 634 635 return iboxVers.InboxVersion, convs, nil 636 } 637 638 func (i *Inbox) GetConversation(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, err Error) { 639 var ierr error 640 defer i.Trace(ctx, &ierr, fmt.Sprintf("GetConversation(%s,%s)", uid, convID))() 641 defer func() { ierr = i.castInternalError(err) }() 642 _, iboxRes, err := i.Read(ctx, uid, &chat1.GetInboxQuery{ 643 ConvID: &convID, 644 }) 645 if err != nil { 646 return res, err 647 } 648 if len(iboxRes) != 1 { 649 return res, MissError{} 650 } 651 return iboxRes[0], nil 652 } 653 654 func (i *Inbox) Read(ctx context.Context, uid gregor1.UID, query *chat1.GetInboxQuery) (vers chat1.InboxVers, res []types.RemoteConversation, err Error) { 655 var ierr error 656 defer i.Trace(ctx, &ierr, fmt.Sprintf("Read(%s)", uid))() 657 defer func() { ierr = i.castInternalError(err) }() 658 locks.Inbox.Lock() 659 defer locks.Inbox.Unlock() 660 defer i.maybeNuke(ctx, func() Error { return err }, uid) 661 662 iboxVers, err := i.readDiskVersions(ctx, uid, true) 663 if err != nil { 664 if _, ok := err.(MissError); ok { 665 i.Debug(ctx, "Read: miss: no inbox versions found") 666 } 667 return 0, nil, err 668 } 669 var convs []types.RemoteConversation 670 if query != nil && (query.ConvID != nil || len(query.ConvIDs) > 0) { 671 convIDs := query.ConvIDs 672 if query.ConvID != nil { 673 convIDs = append(convIDs, *query.ConvID) 674 } 675 if convs, err = i.readConvs(ctx, uid, convIDs); err != nil { 676 return 0, nil, err 677 } 678 } else { 679 iboxIndex, err := i.readDiskIndex(ctx, uid, true) 680 if err != nil { 681 if _, ok := err.(MissError); ok { 682 i.Debug(ctx, "Read: miss: no inbox found") 683 } 684 return 0, nil, err 685 } 686 687 // Check to make sure query parameters have been seen before 688 if !i.queryExists(ctx, uid, iboxIndex, query) { 689 i.Debug(ctx, "Read: miss: query or pagination unknown") 690 return 0, nil, MissError{} 691 } 692 693 if convs, err = i.readConvs(ctx, uid, iboxIndex.ConversationIDs); err != nil { 694 i.Debug(ctx, "Read: unexpected miss on index read: %s", err) 695 return 0, nil, NewInternalError(ctx, i.DebugLabeler, "index out of sync with convs") 696 } 697 } 698 699 // Apply query and pagination 700 res = utils.ApplyInboxQuery(ctx, i.DebugLabeler, query, convs) 701 702 i.Debug(ctx, "Read: hit: version: %d", iboxVers.InboxVersion) 703 return iboxVers.InboxVersion, res, nil 704 } 705 706 func (i *Inbox) clearLocked(ctx context.Context, uid gregor1.UID) (err Error) { 707 var ierr error 708 defer i.Trace(ctx, &ierr, "clearLocked")() 709 defer func() { ierr = i.castInternalError(err) }() 710 var iboxIndex inboxDiskIndex 711 if iboxIndex, err = i.readDiskIndex(ctx, uid, true); err != nil { 712 i.Debug(ctx, "Clear: failed to read index: %s", err) 713 } 714 for _, convID := range iboxIndex.ConversationIDs { 715 if ierr := i.G().LocalChatDb.Delete(i.dbConvKey(uid, convID)); ierr != nil { 716 msg := fmt.Sprintf("error clearing conv: convID: %s err: %s", convID, ierr) 717 err = NewInternalError(ctx, i.DebugLabeler, msg) 718 i.Debug(ctx, msg) 719 } 720 } 721 if ierr := i.G().LocalChatDb.Delete(i.dbVersionsKey(uid)); ierr != nil { 722 msg := fmt.Sprintf("error clearing inbox versions: err: %s", ierr) 723 err = NewInternalError(ctx, i.DebugLabeler, msg) 724 i.Debug(ctx, msg) 725 } 726 if ierr := i.G().LocalChatDb.Delete(i.dbIndexKey(uid)); ierr != nil { 727 msg := fmt.Sprintf("error clearing inbox index: err: %s", ierr) 728 err = NewInternalError(ctx, i.DebugLabeler, msg) 729 i.Debug(ctx, msg) 730 } 731 inboxMemCache.Clear(uid) 732 return err 733 } 734 735 func (i *Inbox) Clear(ctx context.Context, uid gregor1.UID) (err Error) { 736 var ierr error 737 defer i.Trace(ctx, &ierr, "Clear")() 738 defer func() { ierr = i.castInternalError(err) }() 739 locks.Inbox.Lock() 740 defer locks.Inbox.Unlock() 741 return i.clearLocked(ctx, uid) 742 } 743 744 func (i *Inbox) ClearInMemory(ctx context.Context, uid gregor1.UID) (err Error) { 745 var ierr error 746 defer i.Trace(ctx, &ierr, "ClearInMemory")() 747 defer func() { ierr = i.castInternalError(err) }() 748 inboxMemCache.Clear(uid) 749 return nil 750 } 751 752 func (i *Inbox) handleVersion(ctx context.Context, ourvers chat1.InboxVers, updatevers chat1.InboxVers) (chat1.InboxVers, bool, Error) { 753 // Our version is at least as new as this update, let's not continue 754 if updatevers == 0 { 755 // Don't do anything to the version if we are just writing into ourselves, we'll 756 // get the correct version when Gregor bounces the update back at us 757 i.Debug(ctx, "handleVersion: received a self update: ours: %d update: %d", ourvers, updatevers) 758 return ourvers, true, nil 759 } else if ourvers >= updatevers { 760 i.Debug(ctx, "handleVersion: received an old update: ours: %d update: %d", ourvers, updatevers) 761 return ourvers, false, nil 762 } else if updatevers == ourvers+1 { 763 i.Debug(ctx, "handleVersion: received an incremental update: ours: %d update: %d", ourvers, updatevers) 764 return updatevers, true, nil 765 } 766 767 i.Debug(ctx, "handleVersion: received a non-incremental update: ours: %d update: %d", 768 ourvers, updatevers) 769 770 // The update is far ahead of what we have. 771 // Leave our state alone, but request a resync using a VersionMismatchError. 772 return ourvers, false, NewVersionMismatchError(ourvers, updatevers) 773 } 774 775 func (i *Inbox) NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 776 conv chat1.Conversation) (err Error) { 777 var ierr error 778 defer i.Trace(ctx, &ierr, "NewConversation")() 779 defer func() { ierr = i.castInternalError(err) }() 780 locks.Inbox.Lock() 781 defer locks.Inbox.Unlock() 782 defer i.maybeNuke(ctx, func() Error { return err }, uid) 783 layoutChanged := true 784 defer func() { 785 if layoutChanged { 786 i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "new conversation") 787 } 788 }() 789 790 i.Debug(ctx, "NewConversation: vers: %d convID: %s", vers, conv.GetConvID()) 791 iboxVers, err := i.readDiskVersions(ctx, uid, true) 792 if err != nil { 793 if _, ok := err.(MissError); ok { 794 return nil 795 } 796 return err 797 } 798 799 // Check inbox versions, make sure it makes sense (clear otherwise) 800 var cont bool 801 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 802 return err 803 } 804 805 // Do a pass to make sure we don't already know about this convo 806 _, err = i.readConv(ctx, uid, conv.GetConvID()) 807 known := err == nil 808 if !known { 809 iboxIndex, err := i.readDiskIndex(ctx, uid, true) 810 if err != nil { 811 return err 812 } 813 // Find any conversations this guy might supersede and set supersededBy pointer 814 if len(conv.Metadata.Supersedes) > 0 { 815 for _, convID := range iboxIndex.ConversationIDs { 816 iconv, err := i.readConv(ctx, uid, convID) 817 if err != nil { 818 return err 819 } 820 if iconv.Conv.Metadata.FinalizeInfo == nil { 821 continue 822 } 823 for _, super := range conv.Metadata.Supersedes { 824 if iconv.GetConvID().Eq(super.ConversationID) { 825 i.Debug(ctx, "NewConversation: setting supersededBy: target: %s superseder: %s", 826 iconv.ConvIDStr, conv.GetConvID()) 827 iconv.Conv.Metadata.SupersededBy = append(iconv.Conv.Metadata.SupersededBy, conv.Metadata) 828 iconv.Conv.Metadata.Version = vers.ToConvVers() 829 if err := i.writeConv(ctx, uid, iconv, false); err != nil { 830 return err 831 } 832 } 833 } 834 } 835 } 836 if err := i.writeConv(ctx, uid, utils.RemoteConv(conv), false); err != nil { 837 return err 838 } 839 // only chat convs for layout changed 840 layoutChanged = conv.GetTopicType() == chat1.TopicType_CHAT 841 iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, conv.GetConvID()) 842 if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil { 843 return err 844 } 845 } else { 846 i.Debug(ctx, "NewConversation: skipping update, conversation exists in inbox") 847 } 848 849 // Write out to disk 850 iboxVers.InboxVersion = vers 851 return i.writeDiskVersions(ctx, uid, iboxVers) 852 } 853 854 // Return pointers into `convs` for the convs belonging to `teamID`. 855 func (i *Inbox) getConvsForTeam(ctx context.Context, uid gregor1.UID, teamID keybase1.TeamID, 856 index inboxDiskIndex) (res []types.RemoteConversation) { 857 tlfID, err := chat1.TeamIDToTLFID(teamID) 858 if err != nil { 859 i.Debug(ctx, "getConvsForTeam: teamIDToTLFID failed: %v", err) 860 return nil 861 } 862 for _, convID := range index.ConversationIDs { 863 conv, err := i.readConv(ctx, uid, convID) 864 if err != nil { 865 i.Debug(ctx, "getConvsForTeam: failed to get conv: %s", convID) 866 continue 867 } 868 if conv.Conv.GetMembersType() == chat1.ConversationMembersType_TEAM && 869 conv.Conv.Metadata.IdTriple.Tlfid.Eq(tlfID) { 870 res = append(res, conv) 871 } 872 } 873 return res 874 } 875 876 func (i *Inbox) promoteWriter(ctx context.Context, sender gregor1.UID, writers []gregor1.UID) []gregor1.UID { 877 res := make([]gregor1.UID, len(writers)) 878 copy(res, writers) 879 for index, w := range writers { 880 if bytes.Equal(w.Bytes(), sender.Bytes()) { 881 res = append(res[:index], res[index+1:]...) 882 res = append([]gregor1.UID{sender}, res...) 883 return res 884 } 885 } 886 887 i.Debug(ctx, "promoteWriter: failed to promote sender, adding to front: sender: %s", sender) 888 res = append([]gregor1.UID{sender}, res...) 889 return res 890 } 891 892 func (i *Inbox) UpdateInboxVersion(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers) (err Error) { 893 var ierr error 894 defer i.Trace(ctx, &ierr, "UpdateInboxVersion")() 895 defer func() { ierr = i.castInternalError(err) }() 896 locks.Inbox.Lock() 897 defer locks.Inbox.Unlock() 898 defer i.maybeNuke(ctx, func() Error { return err }, uid) 899 ibox, err := i.readDiskVersions(ctx, uid, true) 900 if err != nil { 901 if _, ok := err.(MissError); ok { 902 return nil 903 } 904 return err 905 } 906 var cont bool 907 if vers, cont, err = i.handleVersion(ctx, ibox.InboxVersion, vers); !cont { 908 return err 909 } 910 ibox.InboxVersion = vers 911 return i.writeDiskVersions(ctx, uid, ibox) 912 } 913 914 func (i *Inbox) getConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, found bool, err Error) { 915 conv, err := i.readConv(ctx, uid, convID) 916 if err != nil { 917 if _, ok := err.(MissError); ok { 918 return res, false, nil 919 } 920 return res, false, err 921 } 922 return conv, true, nil 923 } 924 925 func (i *Inbox) IncrementLocalConvVersion(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (err Error) { 926 var ierr error 927 defer i.Trace(ctx, &ierr, "IncrementLocalConvVersion")() 928 defer func() { ierr = i.castInternalError(err) }() 929 locks.Inbox.Lock() 930 defer locks.Inbox.Unlock() 931 defer i.maybeNuke(ctx, func() Error { return err }, uid) 932 conv, found, err := i.getConv(ctx, uid, convID) 933 if err != nil { 934 return err 935 } 936 if !found { 937 i.Debug(ctx, "IncrementLocalConvVersion: no conversation found: convID: %s", convID) 938 return nil 939 } 940 conv.Conv.Metadata.LocalVersion++ 941 return i.writeConv(ctx, uid, conv, false) 942 } 943 944 func (i *Inbox) MarkLocalRead(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 945 msgID chat1.MessageID) (err Error) { 946 var ierr error 947 defer i.Trace(ctx, &ierr, "MarkLocalRead")() 948 defer func() { ierr = i.castInternalError(err) }() 949 locks.Inbox.Lock() 950 defer locks.Inbox.Unlock() 951 defer i.maybeNuke(ctx, func() Error { return err }, uid) 952 conv, found, err := i.getConv(ctx, uid, convID) 953 if err != nil { 954 return err 955 } 956 if !found { 957 i.Debug(ctx, "MarkLocalRead: no conversation found: convID: %s", convID) 958 return nil 959 } 960 conv.LocalReadMsgID = msgID 961 return i.writeConv(ctx, uid, conv, false) 962 } 963 964 func (i *Inbox) Draft(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 965 text *string) (modified bool, err Error) { 966 locks.Inbox.Lock() 967 defer locks.Inbox.Unlock() 968 defer i.maybeNuke(ctx, func() Error { return err }, uid) 969 conv, found, err := i.getConv(ctx, uid, convID) 970 if err != nil { 971 return false, err 972 } 973 if !found { 974 i.Debug(ctx, "Draft: no conversation found: convID: %s", convID) 975 return false, nil 976 } 977 if text == nil && conv.LocalDraft == nil { 978 // don't do anything if we are clearing 979 return false, nil 980 } 981 conv.LocalDraft = text 982 conv.Conv.Metadata.LocalVersion++ 983 return true, i.writeConv(ctx, uid, conv, false) 984 } 985 986 func (i *Inbox) NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 987 convID chat1.ConversationID, msg chat1.MessageBoxed, maxMsgs []chat1.MessageSummary) (err Error) { 988 var ierr error 989 defer i.Trace(ctx, &ierr, "NewMessage")() 990 defer func() { ierr = i.castInternalError(err) }() 991 locks.Inbox.Lock() 992 defer locks.Inbox.Unlock() 993 defer i.maybeNuke(ctx, func() Error { return err }, uid) 994 995 i.Debug(ctx, "NewMessage: vers: %d convID: %s", vers, convID) 996 iboxVers, err := i.readDiskVersions(ctx, uid, true) 997 if err != nil { 998 if _, ok := err.(MissError); ok { 999 return nil 1000 } 1001 return err 1002 } 1003 1004 // Check inbox versions, make sure it makes sense (clear otherwise) 1005 var cont bool 1006 updateVers := vers 1007 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1008 return err 1009 } 1010 1011 // Find conversation 1012 conv, found, err := i.getConv(ctx, uid, convID) 1013 if err != nil { 1014 return err 1015 } 1016 if !found { 1017 i.Debug(ctx, "NewMessage: no conversation found: convID: %s", convID) 1018 // Write out to disk 1019 iboxVers.InboxVersion = vers 1020 return i.writeDiskVersions(ctx, uid, iboxVers) 1021 } 1022 1023 // Update conversation. Use given max messages if the param is non-empty, otherwise just fill 1024 // it in ourselves 1025 if len(maxMsgs) == 0 { 1026 // Check for a delete, if so just auto return a version mismatch to resync. The reason 1027 // is it is tricky to update max messages in this case. NOTE: this update must also not be a 1028 // self update, we only do this clear if the server transmitted the update to us. 1029 if updateVers > 0 { 1030 switch msg.GetMessageType() { 1031 case chat1.MessageType_DELETE, chat1.MessageType_DELETEHISTORY: 1032 i.Debug(ctx, "NewMessage: returning fake version mismatch error because of delete: vers: %d", 1033 vers) 1034 return NewVersionMismatchError(iboxVers.InboxVersion, vers) 1035 } 1036 } 1037 found := false 1038 typ := msg.GetMessageType() 1039 for mindex, maxmsg := range conv.Conv.MaxMsgSummaries { 1040 if maxmsg.GetMessageType() == typ { 1041 conv.Conv.MaxMsgSummaries[mindex] = msg.Summary() 1042 found = true 1043 break 1044 } 1045 } 1046 if !found { 1047 conv.Conv.MaxMsgSummaries = append(conv.Conv.MaxMsgSummaries, msg.Summary()) 1048 } 1049 } else { 1050 i.Debug(ctx, "NewMessage: setting max messages from server payload") 1051 conv.Conv.MaxMsgSummaries = maxMsgs 1052 } 1053 1054 // If we are all up to date on the thread (and the sender is the current user), 1055 // mark this message as read too 1056 if conv.Conv.ReaderInfo.ReadMsgid == conv.Conv.ReaderInfo.MaxMsgid && 1057 bytes.Equal(msg.ClientHeader.Sender.Bytes(), uid) { 1058 conv.Conv.ReaderInfo.ReadMsgid = msg.GetMessageID() 1059 conv.Conv.ReaderInfo.LastSendTime = msg.Ctime() 1060 } 1061 conv.Conv.ReaderInfo.MaxMsgid = msg.GetMessageID() 1062 conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now()) 1063 conv.Conv.Metadata.ActiveList = i.promoteWriter(ctx, msg.ClientHeader.Sender, 1064 conv.Conv.Metadata.ActiveList) 1065 1066 // If we are the sender, adjust the status. 1067 if bytes.Equal(msg.ClientHeader.Sender.Bytes(), uid) && 1068 utils.GetConversationStatusBehavior(conv.Conv.Metadata.Status).SendingRemovesStatus { 1069 conv.Conv.Metadata.Status = chat1.ConversationStatus_UNFILED 1070 } 1071 // If we are a participant, adjust the status. 1072 if utils.GetConversationStatusBehavior(conv.Conv.Metadata.Status).ActivityRemovesStatus { 1073 conv.Conv.Metadata.Status = chat1.ConversationStatus_UNFILED 1074 } 1075 conv.Conv.Metadata.Version = vers.ToConvVers() 1076 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1077 return err 1078 } 1079 1080 // if we have a conv at all, then we want to let any layout engine know about this 1081 // new message 1082 if conv.GetTopicType() == chat1.TopicType_CHAT { 1083 defer i.layoutNotifier.UpdateLayoutFromNewMessage(ctx, conv) 1084 } 1085 // Write out to disk 1086 iboxVers.InboxVersion = vers 1087 return i.writeDiskVersions(ctx, uid, iboxVers) 1088 } 1089 1090 func (i *Inbox) ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1091 convID chat1.ConversationID, msgID chat1.MessageID) (err Error) { 1092 var ierr error 1093 defer i.Trace(ctx, &ierr, "ReadMessage")() 1094 defer func() { ierr = i.castInternalError(err) }() 1095 locks.Inbox.Lock() 1096 defer locks.Inbox.Unlock() 1097 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1098 1099 i.Debug(ctx, "ReadMessage: vers: %d convID: %s", vers, convID) 1100 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1101 if err != nil { 1102 if _, ok := err.(MissError); ok { 1103 return nil 1104 } 1105 return err 1106 } 1107 1108 // Check inbox versions, make sure it makes sense (clear otherwise) 1109 var cont bool 1110 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1111 return err 1112 } 1113 1114 // Find conversation 1115 conv, found, err := i.getConv(ctx, uid, convID) 1116 if err != nil { 1117 return err 1118 } 1119 if !found { 1120 i.Debug(ctx, "ReadMessage: no conversation found: convID: %s", convID) 1121 } else { 1122 // Update conv 1123 i.Debug(ctx, "ReadMessage: updating mtime: readMsgID: %d msgID: %d", conv.Conv.ReaderInfo.ReadMsgid, 1124 msgID) 1125 conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now()) 1126 conv.Conv.ReaderInfo.ReadMsgid = msgID 1127 conv.Conv.Metadata.Version = vers.ToConvVers() 1128 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1129 return err 1130 } 1131 } 1132 1133 // Write out to disk 1134 iboxVers.InboxVersion = vers 1135 return i.writeDiskVersions(ctx, uid, iboxVers) 1136 } 1137 1138 func (i *Inbox) SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1139 convID chat1.ConversationID, status chat1.ConversationStatus) (err Error) { 1140 var ierr error 1141 defer i.Trace(ctx, &ierr, "SetStatus")() 1142 defer func() { ierr = i.castInternalError(err) }() 1143 locks.Inbox.Lock() 1144 defer locks.Inbox.Unlock() 1145 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1146 defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "set status") 1147 1148 i.Debug(ctx, "SetStatus: vers: %d convID: %s", vers, convID) 1149 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1150 if err != nil { 1151 if _, ok := err.(MissError); !ok { 1152 return nil 1153 } 1154 return err 1155 } 1156 1157 // Check inbox versions, make sure it makes sense (clear otherwise) 1158 var cont bool 1159 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1160 return err 1161 } 1162 1163 // Find conversation 1164 conv, found, err := i.getConv(ctx, uid, convID) 1165 if err != nil { 1166 return err 1167 } 1168 if !found { 1169 i.Debug(ctx, "SetStatus: no conversation found: convID: %s", convID) 1170 } else { 1171 conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now()) 1172 conv.Conv.Metadata.Status = status 1173 conv.Conv.Metadata.Version = vers.ToConvVers() 1174 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1175 return err 1176 } 1177 } 1178 1179 // Write out to disk 1180 iboxVers.InboxVersion = vers 1181 return i.writeDiskVersions(ctx, uid, iboxVers) 1182 } 1183 1184 func (i *Inbox) SetAppNotificationSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1185 convID chat1.ConversationID, settings chat1.ConversationNotificationInfo) (err Error) { 1186 var ierr error 1187 defer i.Trace(ctx, &ierr, "SetAppNotificationSettings")() 1188 defer func() { ierr = i.castInternalError(err) }() 1189 locks.Inbox.Lock() 1190 defer locks.Inbox.Unlock() 1191 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1192 1193 i.Debug(ctx, "SetAppNotificationSettings: vers: %d convID: %s", vers, convID) 1194 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1195 if err != nil { 1196 if _, ok := err.(MissError); !ok { 1197 return nil 1198 } 1199 return err 1200 } 1201 // Check inbox versions, make sure it makes sense (clear otherwise) 1202 var cont bool 1203 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1204 return err 1205 } 1206 1207 // Find conversation 1208 conv, found, err := i.getConv(ctx, uid, convID) 1209 if err != nil { 1210 return err 1211 } 1212 if !found { 1213 i.Debug(ctx, "SetAppNotificationSettings: no conversation found: convID: %s", convID) 1214 } else { 1215 for apptype, kindMap := range settings.Settings { 1216 for kind, enabled := range kindMap { 1217 conv.Conv.Notifications.Settings[apptype][kind] = enabled 1218 } 1219 } 1220 conv.Conv.Notifications.ChannelWide = settings.ChannelWide 1221 conv.Conv.Metadata.Version = vers.ToConvVers() 1222 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1223 return err 1224 } 1225 } 1226 1227 // Write out to disk 1228 iboxVers.InboxVersion = vers 1229 return i.writeDiskVersions(ctx, uid, iboxVers) 1230 } 1231 1232 // Mark the expunge on the stored inbox 1233 // The inbox Expunge tag is kept up to date for retention but not for delete-history. 1234 // Does not delete any messages. Relies on separate server mechanism to delete clear max messages. 1235 func (i *Inbox) Expunge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1236 convID chat1.ConversationID, expunge chat1.Expunge, maxMsgs []chat1.MessageSummary) (err Error) { 1237 var ierr error 1238 defer i.Trace(ctx, &ierr, "Expunge")() 1239 defer func() { ierr = i.castInternalError(err) }() 1240 locks.Inbox.Lock() 1241 defer locks.Inbox.Unlock() 1242 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1243 1244 i.Debug(ctx, "Expunge: vers: %d convID: %s", vers, convID) 1245 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1246 if err != nil { 1247 if _, ok := err.(MissError); !ok { 1248 return nil 1249 } 1250 return err 1251 } 1252 // Check inbox versions, make sure it makes sense (clear otherwise) 1253 var cont bool 1254 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1255 return err 1256 } 1257 1258 // Find conversation 1259 conv, found, err := i.getConv(ctx, uid, convID) 1260 if err != nil { 1261 return err 1262 } 1263 if !found { 1264 i.Debug(ctx, "Expunge: no conversation found: convID: %s", convID) 1265 } else { 1266 conv.Conv.Expunge = expunge 1267 conv.Conv.Metadata.Version = vers.ToConvVers() 1268 1269 if len(maxMsgs) == 0 { 1270 // Expunge notifications should always come with max msgs. 1271 i.Debug(ctx, 1272 "Expunge: returning fake version mismatch error because of missing maxMsgs: vers: %d", vers) 1273 return NewVersionMismatchError(iboxVers.InboxVersion, vers) 1274 } 1275 1276 i.Debug(ctx, "Expunge: setting max messages from server payload") 1277 conv.Conv.MaxMsgSummaries = maxMsgs 1278 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1279 return err 1280 } 1281 } 1282 1283 // Write out to disk 1284 iboxVers.InboxVersion = vers 1285 return i.writeDiskVersions(ctx, uid, iboxVers) 1286 } 1287 1288 func (i *Inbox) SubteamRename(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1289 convIDs []chat1.ConversationID) (err Error) { 1290 var ierr error 1291 defer i.Trace(ctx, &ierr, "SubteamRename")() 1292 defer func() { ierr = i.castInternalError(err) }() 1293 locks.Inbox.Lock() 1294 defer locks.Inbox.Unlock() 1295 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1296 var layoutConvs []types.RemoteConversation 1297 defer func() { 1298 i.layoutNotifier.UpdateLayoutFromSubteamRename(ctx, layoutConvs) 1299 }() 1300 1301 i.Debug(ctx, "SubteamRename: vers: %d convIDs: %d", vers, len(convIDs)) 1302 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1303 if err != nil { 1304 if _, ok := err.(MissError); !ok { 1305 return nil 1306 } 1307 return err 1308 } 1309 // Check inbox versions, make sure it makes sense (clear otherwise) 1310 var cont bool 1311 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1312 return err 1313 } 1314 1315 // Update convs 1316 for _, convID := range convIDs { 1317 conv, found, err := i.getConv(ctx, uid, convID) 1318 if err != nil { 1319 return err 1320 } 1321 if !found { 1322 i.Debug(ctx, "SubteamRename: no conversation found: convID: %s", convID) 1323 continue 1324 } 1325 layoutConvs = append(layoutConvs, conv) 1326 conv.Conv.Metadata.Version = vers.ToConvVers() 1327 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1328 return err 1329 } 1330 } 1331 1332 // Write out to disk 1333 iboxVers.InboxVersion = vers 1334 return i.writeDiskVersions(ctx, uid, iboxVers) 1335 } 1336 1337 func (i *Inbox) SetConvRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1338 convID chat1.ConversationID, policy chat1.RetentionPolicy) (err Error) { 1339 var ierr error 1340 defer i.Trace(ctx, &ierr, "SetConvRetention")() 1341 defer func() { ierr = i.castInternalError(err) }() 1342 locks.Inbox.Lock() 1343 defer locks.Inbox.Unlock() 1344 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1345 1346 i.Debug(ctx, "SetConvRetention: vers: %d convID: %s", vers, convID) 1347 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1348 if err != nil { 1349 if _, ok := err.(MissError); !ok { 1350 return nil 1351 } 1352 return err 1353 } 1354 // Check inbox versions, make sure it makes sense (clear otherwise) 1355 var cont bool 1356 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1357 return err 1358 } 1359 1360 // Find conversation 1361 conv, found, err := i.getConv(ctx, uid, convID) 1362 if err != nil { 1363 return err 1364 } 1365 if !found { 1366 i.Debug(ctx, "SetConvRetention: no conversation found: convID: %s", convID) 1367 } else { 1368 conv.Conv.ConvRetention = &policy 1369 conv.Conv.Metadata.Version = vers.ToConvVers() 1370 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1371 return err 1372 } 1373 } 1374 1375 // Write out to disk 1376 iboxVers.InboxVersion = vers 1377 return i.writeDiskVersions(ctx, uid, iboxVers) 1378 } 1379 1380 // Update any local conversations with this team ID. 1381 func (i *Inbox) SetTeamRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1382 teamID keybase1.TeamID, policy chat1.RetentionPolicy) (res []chat1.ConversationID, err Error) { 1383 var ierr error 1384 defer i.Trace(ctx, &ierr, "SetTeamRetention")() 1385 defer func() { ierr = i.castInternalError(err) }() 1386 locks.Inbox.Lock() 1387 defer locks.Inbox.Unlock() 1388 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1389 1390 i.Debug(ctx, "SetTeamRetention: vers: %d teamID: %s", vers, teamID) 1391 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1392 if err != nil { 1393 if _, ok := err.(MissError); !ok { 1394 return res, nil 1395 } 1396 return res, err 1397 } 1398 iboxIndex, err := i.readDiskIndex(ctx, uid, true) 1399 if err != nil { 1400 if _, ok := err.(MissError); !ok { 1401 return res, nil 1402 } 1403 return res, err 1404 } 1405 // Check inbox versions, make sure it makes sense (clear otherwise) 1406 var cont bool 1407 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1408 return res, err 1409 } 1410 1411 // Update conversations 1412 convs := i.getConvsForTeam(ctx, uid, teamID, iboxIndex) 1413 for _, conv := range convs { 1414 conv.Conv.TeamRetention = &policy 1415 conv.Conv.Metadata.Version = vers.ToConvVers() 1416 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1417 return res, err 1418 } 1419 res = append(res, conv.Conv.GetConvID()) 1420 } 1421 1422 // Write out to disk 1423 iboxVers.InboxVersion = vers 1424 err = i.writeDiskVersions(ctx, uid, iboxVers) 1425 return res, err 1426 } 1427 1428 func (i *Inbox) SetConvSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1429 convID chat1.ConversationID, convSettings *chat1.ConversationSettings) (err Error) { 1430 var ierr error 1431 defer i.Trace(ctx, &ierr, "SetConvSettings")() 1432 defer func() { ierr = i.castInternalError(err) }() 1433 locks.Inbox.Lock() 1434 defer locks.Inbox.Unlock() 1435 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1436 1437 i.Debug(ctx, "SetConvSettings: vers: %d convID: %s", vers, convID) 1438 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1439 if err != nil { 1440 if _, ok := err.(MissError); !ok { 1441 return nil 1442 } 1443 return err 1444 } 1445 // Check inbox versions, make sure it makes sense (clear otherwise) 1446 var cont bool 1447 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1448 return err 1449 } 1450 1451 // Find conversation 1452 conv, found, err := i.getConv(ctx, uid, convID) 1453 if !found { 1454 i.Debug(ctx, "SetConvSettings: no conversation found: convID: %s", convID) 1455 } else { 1456 conv.Conv.ConvSettings = convSettings 1457 conv.Conv.Metadata.Version = vers.ToConvVers() 1458 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1459 return err 1460 } 1461 } 1462 1463 // Write out to disk 1464 iboxVers.InboxVersion = vers 1465 return i.writeDiskVersions(ctx, uid, iboxVers) 1466 } 1467 1468 func (i *Inbox) UpgradeKBFSToImpteam(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1469 convID chat1.ConversationID) (err Error) { 1470 var ierr error 1471 defer i.Trace(ctx, &ierr, "UpgradeKBFSToImpteam")() 1472 defer func() { ierr = i.castInternalError(err) }() 1473 locks.Inbox.Lock() 1474 defer locks.Inbox.Unlock() 1475 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1476 1477 i.Debug(ctx, "UpgradeKBFSToImpteam: vers: %d convID: %s", vers, convID) 1478 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1479 if err != nil { 1480 if _, ok := err.(MissError); !ok { 1481 return nil 1482 } 1483 return err 1484 } 1485 // Check inbox versions, make sure it makes sense (clear otherwise) 1486 var cont bool 1487 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1488 return err 1489 } 1490 1491 // Find conversation 1492 conv, found, err := i.getConv(ctx, uid, convID) 1493 if !found { 1494 i.Debug(ctx, "UpgradeKBFSToImpteam: no conversation found: convID: %s", convID) 1495 } else { 1496 conv.Conv.Metadata.MembersType = chat1.ConversationMembersType_IMPTEAMUPGRADE 1497 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1498 return err 1499 } 1500 } 1501 1502 // Write out to disk 1503 iboxVers.InboxVersion = vers 1504 return i.writeDiskVersions(ctx, uid, iboxVers) 1505 } 1506 1507 func (i *Inbox) TeamTypeChanged(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1508 convID chat1.ConversationID, teamType chat1.TeamType, notifInfo *chat1.ConversationNotificationInfo) (err Error) { 1509 var ierr error 1510 defer i.Trace(ctx, &ierr, "TeamTypeChanged")() 1511 defer func() { ierr = i.castInternalError(err) }() 1512 locks.Inbox.Lock() 1513 defer locks.Inbox.Unlock() 1514 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1515 defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "team type") 1516 1517 i.Debug(ctx, "TeamTypeChanged: vers: %d convID: %s typ: %v", vers, convID, teamType) 1518 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1519 if err != nil { 1520 if _, ok := err.(MissError); !ok { 1521 return nil 1522 } 1523 return err 1524 } 1525 // Check inbox versions, make sure it makes sense (clear otherwise) 1526 var cont bool 1527 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1528 return err 1529 } 1530 1531 // Find conversation 1532 conv, found, err := i.getConv(ctx, uid, convID) 1533 if !found { 1534 i.Debug(ctx, "TeamTypeChanged: no conversation found: convID: %s", convID) 1535 } else { 1536 conv.Conv.Notifications = notifInfo 1537 conv.Conv.Metadata.TeamType = teamType 1538 conv.Conv.Metadata.Version = vers.ToConvVers() 1539 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1540 return err 1541 } 1542 } 1543 1544 // Write out to disk 1545 iboxVers.InboxVersion = vers 1546 return i.writeDiskVersions(ctx, uid, iboxVers) 1547 } 1548 1549 func (i *Inbox) TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1550 convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) (err Error) { 1551 var ierr error 1552 defer i.Trace(ctx, &ierr, "TlfFinalize")() 1553 defer func() { ierr = i.castInternalError(err) }() 1554 locks.Inbox.Lock() 1555 defer locks.Inbox.Unlock() 1556 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1557 1558 i.Debug(ctx, "TlfFinalize: vers: %d convIDs: %v finalizeInfo: %v", vers, convIDs, finalizeInfo) 1559 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1560 if err != nil { 1561 if _, ok := err.(MissError); ok { 1562 return nil 1563 } 1564 return err 1565 } 1566 1567 // Check inbox versions, make sure it makes sense (clear otherwise) 1568 var cont bool 1569 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1570 return err 1571 } 1572 1573 for _, convID := range convIDs { 1574 // Find conversation 1575 conv, found, err := i.getConv(ctx, uid, convID) 1576 if err != nil { 1577 return err 1578 } 1579 if !found { 1580 i.Debug(ctx, "TlfFinalize: no conversation found: convID: %s", convID) 1581 continue 1582 } 1583 conv.Conv.Metadata.FinalizeInfo = &finalizeInfo 1584 conv.Conv.Metadata.Version = vers.ToConvVers() 1585 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1586 return err 1587 } 1588 } 1589 1590 // Write out to disk 1591 iboxVers.InboxVersion = vers 1592 return i.writeDiskVersions(ctx, uid, iboxVers) 1593 } 1594 1595 func (i *Inbox) Version(ctx context.Context, uid gregor1.UID) (vers chat1.InboxVers, err Error) { 1596 var ierr error 1597 defer i.Trace(ctx, &ierr, "Version")() 1598 defer func() { ierr = i.castInternalError(err) }() 1599 locks.Inbox.Lock() 1600 defer locks.Inbox.Unlock() 1601 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1602 ibox, err := i.readDiskVersions(ctx, uid, true) 1603 if err != nil { 1604 if _, ok := err.(MissError); ok { 1605 return 0, nil 1606 } 1607 return 0, err 1608 } 1609 return ibox.InboxVersion, nil 1610 } 1611 1612 func (i *Inbox) ServerVersion(ctx context.Context, uid gregor1.UID) (vers int, err Error) { 1613 var ierr error 1614 defer i.Trace(ctx, &ierr, "ServerVersion")() 1615 defer func() { ierr = i.castInternalError(err) }() 1616 locks.Inbox.Lock() 1617 defer locks.Inbox.Unlock() 1618 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1619 ibox, err := i.readDiskVersions(ctx, uid, true) 1620 if err != nil { 1621 if _, ok := err.(MissError); ok { 1622 return 0, nil 1623 } 1624 return 0, err 1625 } 1626 vers = ibox.ServerVersion 1627 return vers, nil 1628 } 1629 1630 func (i *Inbox) topicNameChanged(ctx context.Context, oldConv, newConv chat1.Conversation) bool { 1631 oldMsg, oldErr := oldConv.GetMaxMessage(chat1.MessageType_METADATA) 1632 newMsg, newErr := newConv.GetMaxMessage(chat1.MessageType_METADATA) 1633 if oldErr != nil && newErr != nil { 1634 return false 1635 } 1636 if oldErr != newErr { 1637 return true 1638 } 1639 return oldMsg.GetMessageID() != newMsg.GetMessageID() 1640 } 1641 1642 func (i *Inbox) Sync(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convs []chat1.Conversation) (res types.InboxSyncRes, err Error) { 1643 var ierr error 1644 defer i.Trace(ctx, &ierr, "Sync")() 1645 defer func() { ierr = i.castInternalError(err) }() 1646 locks.Inbox.Lock() 1647 defer locks.Inbox.Unlock() 1648 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1649 defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "sync") 1650 1651 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1652 if err != nil { 1653 // Return MissError, since it should be unexpected if are calling this 1654 return res, err 1655 } 1656 iboxIndex, err := i.readDiskIndex(ctx, uid, true) 1657 if err != nil { 1658 // Return MissError, since it should be unexpected if are calling this 1659 return res, err 1660 } 1661 1662 // Sync inbox with new conversations 1663 oldVers := iboxVers.InboxVersion 1664 iboxVers.InboxVersion = vers 1665 convMap := make(map[chat1.ConvIDStr]chat1.Conversation) 1666 for _, conv := range convs { 1667 convMap[conv.GetConvID().ConvIDStr()] = conv 1668 } 1669 for _, convID := range iboxIndex.ConversationIDs { 1670 if newConv, ok := convMap[convID.ConvIDStr()]; ok { 1671 oldConv, err := i.readConv(ctx, uid, convID) 1672 if err != nil { 1673 if _, ok := err.(MissError); ok { 1674 // just keep going if we don't have it 1675 continue 1676 } 1677 return res, err 1678 } 1679 if oldConv.Conv.Metadata.TeamType != newConv.Metadata.TeamType { 1680 // Changing the team type might be hard for clients of the inbox system to process, 1681 // so call it out so they can know a hard update happened here. 1682 res.TeamTypeChanged = true 1683 } 1684 if oldConv.Conv.Metadata.MembersType != newConv.Metadata.MembersType { 1685 res.MembersTypeChanged = append(res.MembersTypeChanged, 1686 oldConv.GetConvID()) 1687 } 1688 if oldConv.Conv.Expunge != newConv.Expunge { 1689 // The earliest point in non-deleted history has moved up. 1690 // Point it out so that convsource can get updated. 1691 res.Expunges = append(res.Expunges, types.InboxSyncResExpunge{ 1692 ConvID: newConv.Metadata.ConversationID, 1693 Expunge: newConv.Expunge, 1694 }) 1695 } 1696 if i.topicNameChanged(ctx, oldConv.Conv, newConv) { 1697 res.TopicNameChanged = append(res.TopicNameChanged, newConv.GetConvID()) 1698 } 1699 delete(convMap, oldConv.ConvIDStr) 1700 oldConv.Conv = newConv 1701 if err := i.writeConv(ctx, uid, oldConv, false); err != nil { 1702 return res, err 1703 } 1704 } 1705 } 1706 i.Debug(ctx, "Sync: adding %d new conversations", len(convMap)) 1707 for _, conv := range convMap { 1708 if err := i.writeConv(ctx, uid, utils.RemoteConv(conv), false); err != nil { 1709 return res, err 1710 } 1711 iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, conv.GetConvID()) 1712 } 1713 if err = i.writeDiskIndex(ctx, uid, iboxIndex); err != nil { 1714 return res, err 1715 } 1716 i.Debug(ctx, "Sync: old vers: %v new vers: %v convs: %d", oldVers, iboxVers.InboxVersion, len(convs)) 1717 if err = i.writeDiskVersions(ctx, uid, iboxVers); err != nil { 1718 return res, err 1719 } 1720 1721 // Filter the conversations for the result 1722 res.FilteredConvs = utils.ApplyInboxQuery(ctx, i.DebugLabeler, &chat1.GetInboxQuery{ 1723 ConvIDs: utils.PluckConvIDs(convs), 1724 }, utils.RemoteConvs(convs)) 1725 1726 return res, nil 1727 } 1728 1729 func (i *Inbox) MembershipUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1730 userJoined []chat1.Conversation, userRemoved []chat1.ConversationMember, 1731 othersJoined []chat1.ConversationMember, othersRemoved []chat1.ConversationMember, 1732 userReset []chat1.ConversationMember, othersReset []chat1.ConversationMember, 1733 teamMemberRoleUpdate *chat1.TeamMemberRoleUpdate) (roleUpdates []chat1.ConversationID, err Error) { 1734 var ierr error 1735 defer i.Trace(ctx, &ierr, "MembershipUpdate")() 1736 defer func() { ierr = i.castInternalError(err) }() 1737 locks.Inbox.Lock() 1738 defer locks.Inbox.Unlock() 1739 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1740 layoutChanged := false 1741 defer func() { 1742 if layoutChanged { 1743 i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "membership") 1744 } 1745 }() 1746 1747 i.Debug(ctx, "MembershipUpdate: updating userJoined: %d userRemoved: %d othersJoined: %d othersRemoved: %d, teamMemberRoleUpdate: %+v", 1748 len(userJoined), len(userRemoved), len(othersJoined), len(othersRemoved), teamMemberRoleUpdate) 1749 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1750 if err != nil { 1751 if _, ok := err.(MissError); ok { 1752 return nil, nil 1753 } 1754 return nil, err 1755 } 1756 iboxIndex, err := i.readDiskIndex(ctx, uid, true) 1757 if err != nil { 1758 if _, ok := err.(MissError); ok { 1759 return nil, nil 1760 } 1761 return nil, err 1762 } 1763 // Check inbox versions, make sure it makes sense (clear otherwise) 1764 var cont bool 1765 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1766 return nil, err 1767 } 1768 1769 // Process our own changes 1770 var ujids []chat1.ConversationID 1771 for _, uj := range userJoined { 1772 i.Debug(ctx, "MembershipUpdate: joined conv: %s", uj.GetConvID()) 1773 conv := utils.RemoteConv(uj) 1774 if err := i.writeConv(ctx, uid, conv, true); err != nil { 1775 return nil, err 1776 } 1777 ujids = append(ujids, conv.GetConvID()) 1778 layoutChanged = layoutChanged || uj.GetTopicType() == chat1.TopicType_CHAT 1779 } 1780 iboxIndex.mergeConvs(ujids) 1781 convIDs := iboxIndex.ConversationIDs 1782 removedMap := make(map[chat1.ConvIDStr]bool) 1783 for _, r := range userRemoved { 1784 i.Debug(ctx, "MembershipUpdate: removing user from: %s", r) 1785 removedMap[r.ConvID.ConvIDStr()] = true 1786 layoutChanged = layoutChanged || r.TopicType == chat1.TopicType_CHAT 1787 } 1788 resetMap := make(map[chat1.ConvIDStr]bool) 1789 for _, r := range userReset { 1790 i.Debug(ctx, "MembershipUpdate: user reset in: %s", r) 1791 resetMap[r.ConvID.ConvIDStr()] = true 1792 } 1793 iboxIndex.ConversationIDs = nil 1794 for _, convID := range convIDs { 1795 dirty := false 1796 conv, err := i.readConv(ctx, uid, convID) 1797 if err != nil { 1798 return nil, err 1799 } 1800 if teamMemberRoleUpdate != nil && conv.Conv.Metadata.IdTriple.Tlfid.Eq(teamMemberRoleUpdate.TlfID) { 1801 conv.Conv.ReaderInfo.UntrustedTeamRole = teamMemberRoleUpdate.Role 1802 conv.Conv.Metadata.LocalVersion++ 1803 roleUpdates = append(roleUpdates, conv.GetConvID()) 1804 dirty = true 1805 } 1806 if removedMap[conv.ConvIDStr] { 1807 conv.Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_LEFT 1808 conv.Conv.Metadata.Version = vers.ToConvVers() 1809 newAllList := make([]gregor1.UID, 0, len(conv.Conv.Metadata.AllList)) 1810 for _, u := range conv.Conv.Metadata.AllList { 1811 if !u.Eq(uid) { 1812 newAllList = append(newAllList, u) 1813 } 1814 } 1815 switch conv.GetMembersType() { 1816 case chat1.ConversationMembersType_TEAM: 1817 default: 1818 conv.Conv.Metadata.AllList = newAllList 1819 } 1820 dirty = true 1821 } else if resetMap[conv.ConvIDStr] { 1822 conv.Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_RESET 1823 conv.Conv.Metadata.Version = vers.ToConvVers() 1824 switch conv.GetMembersType() { 1825 case chat1.ConversationMembersType_TEAM: 1826 // do nothing 1827 default: 1828 // Double check this user isn't already in here 1829 exists := false 1830 for _, u := range conv.Conv.Metadata.ResetList { 1831 if u.Eq(uid) { 1832 exists = true 1833 break 1834 } 1835 } 1836 if !exists { 1837 conv.Conv.Metadata.ResetList = append(conv.Conv.Metadata.ResetList, uid) 1838 } 1839 } 1840 dirty = true 1841 } 1842 if dirty { 1843 if err := i.writeConv(ctx, uid, conv, false); err != nil { 1844 return nil, err 1845 } 1846 } 1847 iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, convID) 1848 } 1849 1850 // Update all lists with other people joining and leaving 1851 for _, oj := range othersJoined { 1852 cp, err := i.readConv(ctx, uid, oj.ConvID) 1853 if err != nil { 1854 continue 1855 } 1856 // Check reset list for this UID, if we find it remove it instead of adding to all list 1857 isReset := false 1858 var resetIndex int 1859 var r gregor1.UID 1860 for resetIndex, r = range cp.Conv.Metadata.ResetList { 1861 if r.Eq(oj.Uid) { 1862 isReset = true 1863 break 1864 } 1865 } 1866 if isReset { 1867 switch cp.Conv.GetMembersType() { 1868 case chat1.ConversationMembersType_TEAM: 1869 default: 1870 cp.Conv.Metadata.ResetList = append(cp.Conv.Metadata.ResetList[:resetIndex], 1871 cp.Conv.Metadata.ResetList[resetIndex+1:]...) 1872 } 1873 } else { 1874 // Double check this user isn't already in here 1875 exists := false 1876 for _, u := range cp.Conv.Metadata.AllList { 1877 if u.Eq(oj.Uid) { 1878 exists = true 1879 break 1880 } 1881 } 1882 if !exists { 1883 switch cp.Conv.GetMembersType() { 1884 case chat1.ConversationMembersType_TEAM: 1885 default: 1886 cp.Conv.Metadata.AllList = append(cp.Conv.Metadata.AllList, oj.Uid) 1887 } 1888 } 1889 } 1890 cp.Conv.Metadata.Version = vers.ToConvVers() 1891 if err := i.writeConv(ctx, uid, cp, false); err != nil { 1892 return nil, err 1893 } 1894 } 1895 for _, or := range othersRemoved { 1896 cp, err := i.readConv(ctx, uid, or.ConvID) 1897 if err != nil { 1898 continue 1899 } 1900 newAllList := make([]gregor1.UID, 0, len(cp.Conv.Metadata.AllList)) 1901 for _, u := range cp.Conv.Metadata.AllList { 1902 if !u.Eq(or.Uid) { 1903 newAllList = append(newAllList, u) 1904 } 1905 } 1906 cp.Conv.Metadata.AllList = newAllList 1907 cp.Conv.Metadata.Version = vers.ToConvVers() 1908 if err := i.writeConv(ctx, uid, cp, false); err != nil { 1909 return nil, err 1910 } 1911 } 1912 for _, or := range othersReset { 1913 cp, err := i.readConv(ctx, uid, or.ConvID) 1914 if err != nil { 1915 continue 1916 } 1917 switch cp.Conv.GetMembersType() { 1918 case chat1.ConversationMembersType_TEAM: 1919 default: 1920 cp.Conv.Metadata.ResetList = append(cp.Conv.Metadata.ResetList, or.Uid) 1921 } 1922 cp.Conv.Metadata.Version = vers.ToConvVers() 1923 if err := i.writeConv(ctx, uid, cp, false); err != nil { 1924 return nil, err 1925 } 1926 } 1927 if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil { 1928 return nil, err 1929 } 1930 iboxVers.InboxVersion = vers 1931 return roleUpdates, i.writeDiskVersions(ctx, uid, iboxVers) 1932 } 1933 1934 func (i *Inbox) ConversationsUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, 1935 convUpdates []chat1.ConversationUpdate) (err Error) { 1936 var ierr error 1937 defer i.Trace(ctx, &ierr, "ConversationsUpdate")() 1938 defer func() { ierr = i.castInternalError(err) }() 1939 locks.Inbox.Lock() 1940 defer locks.Inbox.Unlock() 1941 defer i.maybeNuke(ctx, func() Error { return err }, uid) 1942 1943 if len(convUpdates) == 0 { 1944 return nil 1945 } 1946 1947 layoutChanged := false 1948 defer func() { 1949 if layoutChanged { 1950 i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "existence") 1951 } 1952 }() 1953 1954 i.Debug(ctx, "ConversationsUpdate: updating %d convs", len(convUpdates)) 1955 iboxVers, err := i.readDiskVersions(ctx, uid, true) 1956 if err != nil { 1957 if _, ok := err.(MissError); ok { 1958 return nil 1959 } 1960 return err 1961 } 1962 // Check inbox versions, make sure it makes sense (clear otherwise) 1963 var cont bool 1964 if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont { 1965 return err 1966 } 1967 1968 // Process our own changes 1969 for _, u := range convUpdates { 1970 i.Debug(ctx, "ConversationsUpdate: changed conv: %v", u) 1971 oldConv, err := i.readConv(ctx, uid, u.ConvID) 1972 if err != nil { 1973 i.Debug(ctx, "ConversationsUpdate: skipping conv: %s err: %s", u.ConvID, err) 1974 continue 1975 } 1976 if oldConv.Conv.Metadata.Existence != u.Existence { 1977 layoutChanged = true 1978 } 1979 oldConv.Conv.Metadata.Existence = u.Existence 1980 if err := i.writeConv(ctx, uid, oldConv, false); err != nil { 1981 return err 1982 } 1983 } 1984 1985 iboxVers.InboxVersion = vers 1986 return i.writeDiskVersions(ctx, uid, iboxVers) 1987 } 1988 1989 func (i *Inbox) UpdateLocalMtime(ctx context.Context, uid gregor1.UID, 1990 convUpdates []chat1.LocalMtimeUpdate) (err Error) { 1991 if len(convUpdates) == 0 { 1992 return nil 1993 } 1994 var ierr error 1995 defer i.Trace(ctx, &ierr, "UpdateLocalMtime")() 1996 defer func() { ierr = i.castInternalError(err) }() 1997 locks.Inbox.Lock() 1998 defer locks.Inbox.Unlock() 1999 defer i.maybeNuke(ctx, func() Error { return err }, uid) 2000 var convs []types.RemoteConversation 2001 defer func() { 2002 for _, conv := range convs { 2003 i.layoutNotifier.UpdateLayoutFromNewMessage(ctx, conv) 2004 } 2005 }() 2006 2007 i.Debug(ctx, "UpdateLocalMtime: updating %d convs", len(convUpdates)) 2008 iboxVers, err := i.readDiskVersions(ctx, uid, true) 2009 if err != nil { 2010 if _, ok := err.(MissError); ok { 2011 return nil 2012 } 2013 return err 2014 } 2015 2016 // Process our own changes 2017 for _, u := range convUpdates { 2018 i.Debug(ctx, "UpdateLocalMtime: applying conv update: %v", u) 2019 oldConv, err := i.readConv(ctx, uid, u.ConvID) 2020 if err != nil { 2021 i.Debug(ctx, "UpdateLocalMtime: skipping conv: %s err: %s", u.ConvID, err) 2022 continue 2023 } 2024 oldConv.LocalMtime = u.Mtime 2025 oldConv.Conv.Metadata.LocalVersion++ 2026 if err := i.writeConv(ctx, uid, oldConv, false); err != nil { 2027 return err 2028 } 2029 } 2030 return i.writeDiskVersions(ctx, uid, iboxVers) 2031 } 2032 2033 type InboxVersionSource struct { 2034 globals.Contextified 2035 } 2036 2037 func NewInboxVersionSource(g *globals.Context) *InboxVersionSource { 2038 return &InboxVersionSource{ 2039 Contextified: globals.NewContextified(g), 2040 } 2041 } 2042 2043 func (i *InboxVersionSource) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) { 2044 return NewInbox(i.G()).Version(ctx, uid) 2045 }