github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/storage/storage.go (about) 1 package storage 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 8 "github.com/keybase/client/go/chat/globals" 9 "github.com/keybase/client/go/chat/pager" 10 "github.com/keybase/client/go/chat/types" 11 "github.com/keybase/client/go/chat/utils" 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/chat1" 14 "github.com/keybase/client/go/protocol/gregor1" 15 "github.com/keybase/client/go/protocol/keybase1" 16 "github.com/keybase/clockwork" 17 "github.com/keybase/go-codec/codec" 18 "golang.org/x/net/context" 19 ) 20 21 var maxFetchNum = 1000 22 23 type ResultCollector interface { 24 Push(msg chat1.MessageUnboxed) 25 PushPlaceholder(msgID chat1.MessageID) bool 26 Done() bool 27 Result() []chat1.MessageUnboxed 28 Error(err Error) Error 29 Name() string 30 SetTarget(num int) 31 32 String() string 33 } 34 35 type Storage struct { 36 globals.Contextified 37 utils.DebugLabeler 38 39 engine storageEngine 40 idtracker *msgIDTracker 41 breakTracker *breakTracker 42 delhTracker *delhTracker 43 assetDeleter AssetDeleter 44 clock clockwork.Clock 45 } 46 47 type storageEngine interface { 48 Init(ctx context.Context, key [32]byte, convID chat1.ConversationID, 49 uid gregor1.UID) (context.Context, Error) 50 WriteMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 51 msgs []chat1.MessageUnboxed) Error 52 ReadMessages(ctx context.Context, res ResultCollector, 53 convID chat1.ConversationID, uid gregor1.UID, maxID, minID chat1.MessageID) Error 54 ClearMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 55 msgIDs []chat1.MessageID) Error 56 } 57 58 type AssetDeleter interface { 59 DeleteAssets(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, assets []chat1.Asset) 60 } 61 62 type DummyAssetDeleter struct{} 63 64 func (d DummyAssetDeleter) DeleteAssets(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 65 assets []chat1.Asset) { 66 67 } 68 69 func New(g *globals.Context, assetDeleter AssetDeleter) *Storage { 70 return &Storage{ 71 Contextified: globals.NewContextified(g), 72 engine: newBlockEngine(g), 73 idtracker: newMsgIDTracker(g), 74 breakTracker: newBreakTracker(g), 75 delhTracker: newDelhTracker(g), 76 assetDeleter: assetDeleter, 77 clock: clockwork.NewRealClock(), 78 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Storage", false), 79 } 80 } 81 82 func (s *Storage) setEngine(engine storageEngine) { 83 s.engine = engine 84 } 85 86 func (s *Storage) SetClock(clock clockwork.Clock) { 87 s.clock = clock 88 } 89 90 func (s *Storage) SetAssetDeleter(assetDeleter AssetDeleter) { 91 s.assetDeleter = assetDeleter 92 } 93 94 func makeBlockIndexKey(convID chat1.ConversationID, uid gregor1.UID) libkb.DbKey { 95 return libkb.DbKey{ 96 Typ: libkb.DBChatBlockIndex, 97 Key: fmt.Sprintf("bi:%s:%s", uid, convID), 98 } 99 } 100 101 func encode(input interface{}) ([]byte, error) { 102 mh := codec.MsgpackHandle{WriteExt: true} 103 var data []byte 104 enc := codec.NewEncoderBytes(&data, &mh) 105 if err := enc.Encode(input); err != nil { 106 return nil, err 107 } 108 return data, nil 109 } 110 111 func decode(data []byte, res interface{}) error { 112 mh := codec.MsgpackHandle{WriteExt: true} 113 dec := codec.NewDecoderBytes(data, &mh) 114 err := dec.Decode(res) 115 return err 116 } 117 118 // SimpleResultCollector aggregates all results in a basic way. It is not thread safe. 119 type SimpleResultCollector struct { 120 res []chat1.MessageUnboxed 121 target, cur, curScan int 122 123 // countAll controls whether or not deleted messages should count toward target 124 countAll bool 125 } 126 127 var _ ResultCollector = (*SimpleResultCollector)(nil) 128 129 func (s *SimpleResultCollector) Push(msg chat1.MessageUnboxed) { 130 s.res = append(s.res, msg) 131 if s.countAll || !msg.IsValidDeleted() { 132 s.cur++ 133 } 134 s.curScan++ 135 } 136 137 func (s *SimpleResultCollector) Done() bool { 138 if s.target < 0 { 139 return false 140 } 141 return s.cur >= s.target || s.curScan >= maxFetchNum 142 } 143 144 func (s *SimpleResultCollector) Result() []chat1.MessageUnboxed { 145 return s.res 146 } 147 148 func (s *SimpleResultCollector) Name() string { 149 return "simple" 150 } 151 152 func (s *SimpleResultCollector) String() string { 153 return fmt.Sprintf("[ %s: t: %d c: %d ]", s.Name(), s.target, len(s.res)) 154 } 155 156 func (s *SimpleResultCollector) Error(err Error) Error { 157 if s.target < 0 { 158 // Swallow this error if we are not looking for a target 159 if _, ok := err.(MissError); ok { 160 return nil 161 } 162 } 163 return err 164 } 165 166 func (s *SimpleResultCollector) PushPlaceholder(chat1.MessageID) bool { 167 return false 168 } 169 170 func (s *SimpleResultCollector) SetTarget(num int) { 171 s.target = num 172 } 173 174 func NewSimpleResultCollector(num int, countAll bool) *SimpleResultCollector { 175 return &SimpleResultCollector{ 176 target: num, 177 countAll: countAll, 178 } 179 } 180 181 type InsatiableResultCollector struct { 182 res []chat1.MessageUnboxed 183 } 184 185 var _ ResultCollector = (*InsatiableResultCollector)(nil) 186 187 // InsatiableResultCollector aggregates all messages all the way back. 188 // Its result can include holes. 189 func NewInsatiableResultCollector() *InsatiableResultCollector { 190 return &InsatiableResultCollector{} 191 } 192 193 func (s *InsatiableResultCollector) Push(msg chat1.MessageUnboxed) { 194 s.res = append(s.res, msg) 195 } 196 197 func (s *InsatiableResultCollector) Done() bool { 198 return false 199 } 200 201 func (s *InsatiableResultCollector) Result() []chat1.MessageUnboxed { 202 return s.res 203 } 204 205 func (s *InsatiableResultCollector) Name() string { 206 return "inf" 207 } 208 209 func (s *InsatiableResultCollector) String() string { 210 return fmt.Sprintf("[ %s: c: %d ]", s.Name(), len(s.res)) 211 } 212 213 func (s *InsatiableResultCollector) Error(err Error) Error { 214 return err 215 } 216 217 func (s *InsatiableResultCollector) SetTarget(num int) {} 218 219 func (s *InsatiableResultCollector) PushPlaceholder(chat1.MessageID) bool { 220 // Missing messages are a-ok 221 return true 222 } 223 224 // TypedResultCollector aggregates results with a type constraints. It is not thread safe. 225 type TypedResultCollector struct { 226 res []chat1.MessageUnboxed 227 target, cur, curScan int 228 typmap map[chat1.MessageType]bool 229 } 230 231 var _ ResultCollector = (*TypedResultCollector)(nil) 232 233 func NewTypedResultCollector(num int, typs []chat1.MessageType) *TypedResultCollector { 234 c := TypedResultCollector{ 235 target: num, 236 typmap: make(map[chat1.MessageType]bool), 237 } 238 for _, typ := range typs { 239 c.typmap[typ] = true 240 } 241 return &c 242 } 243 244 func (t *TypedResultCollector) Push(msg chat1.MessageUnboxed) { 245 t.res = append(t.res, msg) 246 if !msg.IsValidDeleted() && t.typmap[msg.GetMessageType()] { 247 t.cur++ 248 } 249 t.curScan++ 250 } 251 252 func (t *TypedResultCollector) Done() bool { 253 if t.target < 0 { 254 return false 255 } 256 return t.cur >= t.target || t.curScan >= maxFetchNum 257 } 258 259 func (t *TypedResultCollector) Result() []chat1.MessageUnboxed { 260 return t.res 261 } 262 263 func (t *TypedResultCollector) Name() string { 264 return "typed" 265 } 266 267 func (t *TypedResultCollector) String() string { 268 return fmt.Sprintf("[ %s: t: %d c: %d (%d types) ]", t.Name(), t.target, t.cur, len(t.typmap)) 269 } 270 271 func (t *TypedResultCollector) Error(err Error) Error { 272 if t.target < 0 { 273 // Swallow this error if we are not looking for a target 274 if _, ok := err.(MissError); ok { 275 return nil 276 } 277 } 278 return err 279 } 280 281 func (t *TypedResultCollector) PushPlaceholder(msgID chat1.MessageID) bool { 282 return false 283 } 284 285 func (t *TypedResultCollector) SetTarget(num int) { 286 t.target = num 287 } 288 289 type HoleyResultCollector struct { 290 ResultCollector 291 292 maxHoles, holes int 293 } 294 295 var _ ResultCollector = (*HoleyResultCollector)(nil) 296 297 func NewHoleyResultCollector(maxHoles int, rc ResultCollector) *HoleyResultCollector { 298 return &HoleyResultCollector{ 299 ResultCollector: rc, 300 maxHoles: maxHoles, 301 } 302 } 303 304 func (h *HoleyResultCollector) PushPlaceholder(msgID chat1.MessageID) bool { 305 if h.holes >= h.maxHoles { 306 return false 307 } 308 309 h.ResultCollector.Push(chat1.NewMessageUnboxedWithPlaceholder(chat1.MessageUnboxedPlaceholder{ 310 MessageID: msgID, 311 })) 312 h.holes++ 313 return true 314 } 315 316 func (h *HoleyResultCollector) Holes() int { 317 return h.holes 318 } 319 320 func (s *Storage) castInternalError(ierr Error) error { 321 err, ok := ierr.(error) 322 if ok { 323 return err 324 } 325 return nil 326 } 327 328 func (s *Storage) Nuke(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID) Error { 329 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 330 defer lock.Release(ctx) 331 return s.maybeNukeLocked(ctx, true /* force */, nil /* error */, convID, uid) 332 } 333 334 func (s *Storage) maybeNukeLocked(ctx context.Context, force bool, err Error, convID chat1.ConversationID, 335 uid gregor1.UID) Error { 336 // Clear index 337 if force || err.ShouldClear() { 338 s.Debug(ctx, "chat local storage corrupted: clearing") 339 if err := s.G().LocalChatDb.Delete(makeBlockIndexKey(convID, uid)); err != nil { 340 s.Debug(ctx, "failed to delete chat index, clearing entire local storage (delete error: %s)", 341 err) 342 if _, err = s.G().LocalChatDb.Nuke(); err != nil { 343 s.Debug(ctx, "failed to delete chat local storage: %s", err) 344 } 345 } 346 if err := s.idtracker.clear(convID, uid); err != nil { 347 s.Debug(ctx, "failed to clear max message storage: %s", err) 348 } 349 if err := s.G().EphemeralTracker.Clear(ctx, convID, uid); err != nil { 350 s.Debug(ctx, "failed to clear ephemeral tracker storage: %s", err) 351 } 352 } 353 return err 354 } 355 356 func (s *Storage) SetMaxMsgID(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 357 msgID chat1.MessageID) (err Error) { 358 var ierr error 359 defer s.Trace(ctx, &ierr, "SetMaxMsgID")() 360 defer func() { ierr = s.castInternalError(err) }() 361 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 362 defer lock.Release(ctx) 363 return s.idtracker.bumpMaxMessageID(ctx, convID, uid, msgID) 364 } 365 366 func (s *Storage) GetMaxMsgID(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID) (maxMsgID chat1.MessageID, err Error) { 367 var ierr error 368 defer s.Trace(ctx, &ierr, "GetMaxMsgID")() 369 defer func() { ierr = s.castInternalError(err) }() 370 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 371 defer lock.Release(ctx) 372 373 if maxMsgID, err = s.idtracker.getMaxMessageID(ctx, convID, uid); err != nil { 374 return maxMsgID, s.maybeNukeLocked(ctx, false, err, convID, uid) 375 } 376 return maxMsgID, nil 377 } 378 379 type UnfurlMergeResult struct { 380 Msg chat1.MessageUnboxed 381 IsMapDelete bool 382 } 383 384 type MergeResult struct { 385 Expunged *chat1.Expunge 386 Exploded []chat1.MessageUnboxed 387 ReactionTargets []chat1.MessageUnboxed 388 UnfurlTargets []UnfurlMergeResult 389 RepliesAffected []chat1.MessageUnboxed 390 } 391 392 type FetchResult struct { 393 Thread chat1.ThreadView 394 Exploded []chat1.MessageUnboxed 395 } 396 397 // Merge requires msgs to be sorted by descending message ID 398 func (s *Storage) Merge(ctx context.Context, 399 conv types.UnboxConversationInfo, uid gregor1.UID, msgs []chat1.MessageUnboxed) (res MergeResult, err Error) { 400 var ierr error 401 defer s.Trace(ctx, &ierr, "Merge")() 402 defer func() { ierr = s.castInternalError(err) }() 403 return s.MergeHelper(ctx, conv, uid, msgs, nil) 404 } 405 406 func (s *Storage) Expunge(ctx context.Context, 407 conv types.UnboxConversationInfo, uid gregor1.UID, expunge chat1.Expunge) (res MergeResult, err Error) { 408 var ierr error 409 defer s.Trace(ctx, &ierr, "Expunge")() 410 defer func() { ierr = s.castInternalError(err) }() 411 // Merge with no messages, just the expunge. 412 return s.MergeHelper(ctx, conv, uid, nil, &expunge) 413 } 414 415 // MergeHelper requires msgs to be sorted by descending message ID 416 // expunge is optional 417 func (s *Storage) MergeHelper(ctx context.Context, 418 conv types.UnboxConversationInfo, uid gregor1.UID, msgs []chat1.MessageUnboxed, expunge *chat1.Expunge) (res MergeResult, err Error) { 419 var ierr error 420 defer s.Trace(ctx, &ierr, "MergeHelper")() 421 defer func() { ierr = s.castInternalError(err) }() 422 convID := conv.GetConvID() 423 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 424 defer lock.Release(ctx) 425 426 s.Debug(ctx, "MergeHelper: convID: %s uid: %s num msgs: %d", convID, uid, len(msgs)) 427 428 // Fetch secret key 429 key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG()) 430 if ierr != nil { 431 return res, MiscError{Msg: "unable to get secret key: " + ierr.Error()} 432 } 433 434 ctx, err = s.engine.Init(ctx, key, convID, uid) 435 if err != nil { 436 return res, err 437 } 438 439 // Write out new data into blocks 440 if err = s.engine.WriteMessages(ctx, convID, uid, msgs); err != nil { 441 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 442 } 443 444 // Update supersededBy pointers 445 updateRes, err := s.updateAllSupersededBy(ctx, convID, uid, msgs) 446 if err != nil { 447 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 448 } 449 res.ReactionTargets = updateRes.reactionTargets 450 res.UnfurlTargets = updateRes.unfurlTargets 451 res.RepliesAffected = updateRes.repliesAffected 452 453 if err = s.updateMinDeletableMessage(ctx, convID, uid, msgs); err != nil { 454 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 455 } 456 457 // Process any DeleteHistory messages 458 expunged, err := s.handleDeleteHistory(ctx, conv, uid, msgs, expunge) 459 if err != nil { 460 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 461 } 462 res.Expunged = expunged 463 464 exploded, err := s.explodeExpiredMessages(ctx, convID, uid, msgs) 465 if err != nil { 466 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 467 } 468 res.Exploded = exploded 469 470 // Update max msg ID if needed 471 if len(msgs) > 0 { 472 if err := s.idtracker.bumpMaxMessageID(ctx, convID, uid, msgs[0].GetMessageID()); err != nil { 473 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 474 } 475 } 476 477 // queue search index update in the background 478 go func(ctx context.Context) { 479 err := s.G().Indexer.Add(ctx, convID, msgs) 480 if err != nil { 481 s.Debug(ctx, "Error adding to indexer: %+v", err) 482 } 483 }(globals.BackgroundChatCtx(ctx, s.G())) 484 485 return res, nil 486 } 487 488 type updateAllSupersededByRes struct { 489 reactionTargets []chat1.MessageUnboxed 490 unfurlTargets []UnfurlMergeResult 491 repliesAffected []chat1.MessageUnboxed 492 } 493 494 func (s *Storage) isReply(msg chat1.MessageUnboxed) *chat1.MessageID { 495 if !msg.IsValid() { 496 return nil 497 } 498 body := msg.Valid().MessageBody 499 if body.IsType(chat1.MessageType_TEXT) && body.Text().ReplyTo != nil && *body.Text().ReplyTo > 0 { 500 return body.Text().ReplyTo 501 } 502 return nil 503 } 504 505 func (s *Storage) updateAllSupersededBy(ctx context.Context, convID chat1.ConversationID, 506 uid gregor1.UID, inMsgs []chat1.MessageUnboxed) (res updateAllSupersededByRes, err Error) { 507 s.Debug(ctx, "updateSupersededBy: num msgs: %d", len(inMsgs)) 508 // Do a pass over all the messages and update supersededBy pointers 509 510 var allAssets []chat1.Asset 511 var allPurged []chat1.MessageUnboxed 512 // We return a set of reaction targets that have been updated 513 updatedReactionTargets := map[chat1.MessageID]chat1.MessageUnboxed{} 514 // Unfurl targets 515 updatedUnfurlTargets := make(map[chat1.MessageID]UnfurlMergeResult) 516 repliesAffected := map[chat1.MessageID]chat1.MessageUnboxed{} 517 518 // Sort in reverse order so this playback works as it would have if we received these 519 // in real-time 520 msgs := make([]chat1.MessageUnboxed, len(inMsgs)) 521 copy(msgs, inMsgs) 522 sort.Slice(msgs, func(i, j int) bool { 523 return msgs[i].GetMessageID() < msgs[j].GetMessageID() 524 }) 525 newMsgMap := make(map[chat1.MessageID]chat1.MessageUnboxed) 526 getMessage := func(msgID chat1.MessageID) (*chat1.MessageUnboxed, Error) { 527 stored, ok := newMsgMap[msgID] 528 if ok { 529 return &stored, nil 530 } 531 return s.getMessage(ctx, convID, uid, msgID) 532 } 533 for _, msg := range msgs { 534 msgid := msg.GetMessageID() 535 if !msg.IsValid() { 536 s.Debug(ctx, "updateSupersededBy: skipping potential superseder marked as not valid: %v", msg.DebugString()) 537 continue 538 } 539 540 supersededIDs, ierr := utils.GetSupersedes(msg) 541 if ierr != nil { 542 continue 543 } 544 if replyID := s.isReply(msg); replyID != nil { 545 supersededIDs = append(supersededIDs, *replyID) 546 } 547 // Set all supersedes targets 548 for _, supersededID := range supersededIDs { 549 if supersededID == 0 { 550 s.Debug(ctx, "updateSupersededBy: skipping invalid supersededID: %v for msg: %v", supersededID, msg.DebugString()) 551 continue 552 } 553 554 s.Debug(ctx, "updateSupersededBy: msg: %v supersedes: %v", msg.DebugString(), supersededID) 555 // Read superseded msg 556 superMsg, err := getMessage(supersededID) 557 if err != nil { 558 return res, err 559 } 560 if superMsg == nil { 561 continue 562 } 563 564 // Update supersededBy and reactionIDs on the target message if we 565 // have it. If the superseder is a deletion, delete the body as 566 // well. If we are deleting a reaction, update the reaction's 567 // target message. 568 if superMsg.IsValid() { 569 s.Debug(ctx, "updateSupersededBy: writing: id: %d superseded: %d", msgid, supersededID) 570 mvalid := superMsg.Valid() 571 572 switch msg.GetMessageType() { 573 case chat1.MessageType_TEXT: 574 mvalid.ServerHeader.Replies = append(mvalid.ServerHeader.Replies, msg.GetMessageID()) 575 newMsg := chat1.NewMessageUnboxedWithValid(mvalid) 576 newMsgMap[newMsg.GetMessageID()] = newMsg 577 case chat1.MessageType_UNFURL: 578 unfurl := msg.Valid().MessageBody.Unfurl() 579 utils.SetUnfurl(&mvalid, msg.GetMessageID(), unfurl.Unfurl) 580 newMsg := chat1.NewMessageUnboxedWithValid(mvalid) 581 newMsgMap[newMsg.GetMessageID()] = newMsg 582 updatedUnfurlTargets[superMsg.GetMessageID()] = UnfurlMergeResult{ 583 Msg: newMsg, 584 IsMapDelete: false, 585 } 586 case chat1.MessageType_REACTION: 587 // If we haven't modified any reaction data, we don't want 588 // to send it up for a notification. 589 var reactionUpdate bool 590 // reactions don't update SupersededBy, instead they rely 591 // on ReactionIDs 592 mvalid.ServerHeader.ReactionIDs, reactionUpdate = 593 s.updateReactionIDs(mvalid.ServerHeader.ReactionIDs, msgid) 594 newMsg := chat1.NewMessageUnboxedWithValid(mvalid) 595 newMsgMap[newMsg.GetMessageID()] = newMsg 596 if reactionUpdate { 597 updatedReactionTargets[superMsg.GetMessageID()] = newMsg 598 } 599 case chat1.MessageType_DELETE: 600 mvalid.ServerHeader.SupersededBy = msgid 601 s.updateRepliesAffected(ctx, convID, uid, mvalid.ServerHeader.Replies, repliesAffected) 602 switch superMsg.GetMessageType() { 603 case chat1.MessageType_UNFURL: 604 updatedTarget, err := s.updateUnfurlTargetOnDelete(ctx, convID, uid, *superMsg) 605 if err != nil { 606 s.Debug(ctx, "updateSupersededBy: failed to update unfurl target: %s", err) 607 } else { 608 updatedUnfurlTargets[updatedTarget.GetMessageID()] = UnfurlMergeResult{ 609 Msg: updatedTarget, 610 IsMapDelete: utils.IsMapUnfurl(*superMsg), 611 } 612 newMsgMap[updatedTarget.GetMessageID()] = updatedTarget 613 } 614 case chat1.MessageType_REACTION: 615 // We have to find the message we are reacting to and 616 // update it's ReactionIDs as well. 617 newTargetMsg, reactionUpdate, err := s.updateReactionTargetOnDelete(ctx, convID, uid, 618 superMsg) 619 if err != nil { 620 return res, err 621 } else if newTargetMsg != nil { 622 if reactionUpdate { 623 updatedReactionTargets[newTargetMsg.GetMessageID()] = *newTargetMsg 624 } 625 newMsgMap[newTargetMsg.GetMessageID()] = *newTargetMsg 626 } 627 } 628 msgPurged, assets := s.purgeMessage(mvalid) 629 allPurged = append(allPurged, *superMsg) 630 allAssets = append(allAssets, assets...) 631 newMsgMap[msgPurged.GetMessageID()] = msgPurged 632 case chat1.MessageType_EDIT: 633 s.updateRepliesAffected(ctx, convID, uid, mvalid.ServerHeader.Replies, repliesAffected) 634 fallthrough 635 default: 636 mvalid.ServerHeader.SupersededBy = msgid 637 newMsg := chat1.NewMessageUnboxedWithValid(mvalid) 638 newMsgMap[newMsg.GetMessageID()] = newMsg 639 } 640 } else { 641 s.Debug(ctx, "updateSupersededBy: skipping id: %d, it is stored as an error", 642 superMsg.GetMessageID()) 643 } 644 } 645 } 646 647 // Write out all the modified messages in one shot 648 newMsgs := make([]chat1.MessageUnboxed, 0, len(newMsgMap)) 649 for _, msg := range newMsgMap { 650 newMsgs = append(newMsgs, msg) 651 } 652 sort.Slice(newMsgs, func(i, j int) bool { 653 return newMsgs[i].GetMessageID() > newMsgs[j].GetMessageID() 654 }) 655 if err = s.engine.WriteMessages(ctx, convID, uid, newMsgs); err != nil { 656 return res, err 657 } 658 659 // queue asset deletions in the background 660 s.assetDeleter.DeleteAssets(ctx, uid, convID, allAssets) 661 // queue search index update in the background 662 go func(ctx context.Context) { 663 err := s.G().Indexer.Remove(ctx, convID, allPurged) 664 if err != nil { 665 s.Debug(ctx, "Error removing from indexer: %+v", err) 666 } 667 }(globals.BackgroundChatCtx(ctx, s.G())) 668 var flattenedUnfurlTargets []UnfurlMergeResult 669 for _, r := range updatedUnfurlTargets { 670 flattenedUnfurlTargets = append(flattenedUnfurlTargets, r) 671 } 672 return updateAllSupersededByRes{ 673 reactionTargets: s.flatten(updatedReactionTargets), 674 unfurlTargets: flattenedUnfurlTargets, 675 repliesAffected: s.flatten(repliesAffected), 676 }, nil 677 } 678 679 func (s *Storage) flatten(m map[chat1.MessageID]chat1.MessageUnboxed) (res []chat1.MessageUnboxed) { 680 for _, msg := range m { 681 res = append(res, msg) 682 } 683 return res 684 } 685 686 func (s *Storage) updateMinDeletableMessage(ctx context.Context, convID chat1.ConversationID, 687 uid gregor1.UID, msgs []chat1.MessageUnboxed) Error { 688 689 de := func(format string, args ...interface{}) { 690 s.Debug(ctx, "updateMinDeletableMessage: "+fmt.Sprintf(format, args...)) 691 } 692 693 // The min deletable message ID in this new batch of messages. 694 var minDeletableMessageBatch *chat1.MessageID 695 for _, msg := range msgs { 696 msgid := msg.GetMessageID() 697 if !msg.IsValid() { 698 continue 699 } 700 if !chat1.IsDeletableByDeleteHistory(msg.GetMessageType()) { 701 continue 702 } 703 if msg.Valid().MessageBody.IsNil() { 704 continue 705 } 706 if minDeletableMessageBatch == nil || msgid < *minDeletableMessageBatch { 707 minDeletableMessageBatch = &msgid 708 } 709 } 710 711 // Update the tracker to min(mem, batch) 712 if minDeletableMessageBatch != nil { 713 mem, err := s.delhTracker.getEntry(ctx, convID, uid) 714 switch err.(type) { 715 case nil: 716 if mem.MinDeletableMessage > 0 && *minDeletableMessageBatch >= mem.MinDeletableMessage { 717 // no need to update 718 return nil 719 } 720 case MissError: 721 // We have no memory 722 default: 723 return err 724 } 725 726 err = s.delhTracker.setMinDeletableMessage(ctx, convID, uid, *minDeletableMessageBatch) 727 if err != nil { 728 de("failed to store delh track: %v", err) 729 } 730 } 731 732 return nil 733 } 734 735 // Apply any new DeleteHistory from msgs. 736 // Returns a non-nil expunge if deletes happened. 737 // Shortcircuits so it's ok to call a lot. 738 // The actual effect will be to delete upto the max of `expungeExplicit` (which can be nil) 739 // 740 // and the DeleteHistory-type messages. 741 func (s *Storage) handleDeleteHistory(ctx context.Context, conv types.UnboxConversationInfo, 742 uid gregor1.UID, msgs []chat1.MessageUnboxed, expungeExplicit *chat1.Expunge) (*chat1.Expunge, Error) { 743 744 de := func(format string, args ...interface{}) { 745 s.Debug(ctx, "handleDeleteHistory: "+fmt.Sprintf(format, args...)) 746 } 747 748 // Find the DeleteHistory message with the maximum upto value. 749 expungeActive := expungeExplicit 750 for _, msg := range msgs { 751 msgid := msg.GetMessageID() 752 if !msg.IsValid() { 753 de("skipping message marked as not valid: %v", msg.DebugString()) 754 continue 755 } 756 if msg.GetMessageType() != chat1.MessageType_DELETEHISTORY { 757 continue 758 } 759 mvalid := msg.Valid() 760 bodyType, err := mvalid.MessageBody.MessageType() 761 if err != nil { 762 de("skipping corrupted message body: %v", err) 763 continue 764 } 765 if bodyType != chat1.MessageType_DELETEHISTORY { 766 de("skipping wrong message body type: %v", err) 767 continue 768 } 769 delh := mvalid.MessageBody.Deletehistory() 770 de("found DeleteHistory: id:%v upto:%v", msgid, delh.Upto) 771 if delh.Upto == 0 { 772 de("skipping malformed delh") 773 continue 774 } 775 776 if expungeActive == nil || (delh.Upto > expungeActive.Upto) { 777 expungeActive = &chat1.Expunge{ 778 Basis: mvalid.ServerHeader.MessageID, 779 Upto: delh.Upto, 780 } 781 } 782 } 783 784 // Noop if there is no Expunge or DeleteHistory messages 785 if expungeActive == nil { 786 return nil, nil 787 } 788 if expungeActive.Upto == 0 { 789 return nil, nil 790 } 791 792 mem, err := s.delhTracker.getEntry(ctx, conv.GetConvID(), uid) 793 switch err.(type) { 794 case nil: 795 if mem.MaxDeleteHistoryUpto >= expungeActive.Upto { 796 // No-op if the effect has already been applied locally 797 de("skipping delh with no new effect: (upto local:%v >= msg:%v)", mem.MaxDeleteHistoryUpto, expungeActive.Upto) 798 return nil, nil 799 } 800 if expungeActive.Upto < mem.MinDeletableMessage { 801 // Record-only if it would delete messages earlier than the local min. 802 de("record-only delh: (%v < %v)", expungeActive.Upto, mem.MinDeletableMessage) 803 err := s.delhTracker.setMaxDeleteHistoryUpto(ctx, conv.GetConvID(), uid, expungeActive.Upto) 804 if err != nil { 805 de("failed to store delh track: %v", err) 806 } 807 return nil, nil 808 } 809 // No shortcuts, fallthrough to apply. 810 case MissError: 811 // We have no memory, assume it needs to be applied 812 default: 813 return nil, err 814 } 815 816 return s.applyExpunge(ctx, conv, uid, *expungeActive) 817 } 818 819 // Apply a delete history. 820 // Returns a non-nil expunge if deletes happened. 821 // Always runs through local messages. 822 func (s *Storage) applyExpunge(ctx context.Context, conv types.UnboxConversationInfo, 823 uid gregor1.UID, expunge chat1.Expunge) (*chat1.Expunge, Error) { 824 825 convID := conv.GetConvID() 826 s.Debug(ctx, "applyExpunge(%v, %v, %v)", convID, uid, expunge.Upto) 827 828 de := func(format string, args ...interface{}) { 829 s.Debug(ctx, "applyExpunge: "+fmt.Sprintf(format, args...)) 830 } 831 832 rc := NewInsatiableResultCollector() // collect all messages 833 err := s.engine.ReadMessages(ctx, rc, convID, uid, expunge.Upto-1, 0) 834 switch err.(type) { 835 case nil: 836 // ok 837 case MissError: 838 de("record-only delh: no local messages") 839 err := s.delhTracker.setMaxDeleteHistoryUpto(ctx, convID, uid, expunge.Upto) 840 if err != nil { 841 de("failed to store delh track: %v", err) 842 } 843 return nil, nil 844 default: 845 return nil, err 846 } 847 848 var allAssets []chat1.Asset 849 var writeback, allPurged []chat1.MessageUnboxed 850 for _, msg := range rc.Result() { 851 mtype := msg.GetMessageType() 852 if !chat1.IsDeletableByDeleteHistory(mtype) { 853 // Skip message types that cannot be deleted this way 854 continue 855 } 856 if !msg.IsValid() { 857 de("skipping invalid msg: %v", msg.DebugString()) 858 continue 859 } 860 mvalid := msg.Valid() 861 if mvalid.MessageBody.IsNil() { 862 continue 863 } 864 // METADATA and HEADLINE messages are only expunged if they are not the 865 // latest max message. 866 switch mtype { 867 case chat1.MessageType_METADATA, 868 chat1.MessageType_HEADLINE: 869 maxMsg, err := conv.GetMaxMessage(mtype) 870 if err != nil { 871 de("delh: %v, not expunging %v", err, msg.DebugString()) 872 continue 873 } else if maxMsg.MsgID == msg.GetMessageID() { 874 de("delh: not expunging %v, latest max message", msg.DebugString()) 875 continue 876 } 877 de("delh: expunging %v, non-max message", msg.DebugString()) 878 default: 879 } 880 881 mvalid.ServerHeader.SupersededBy = expunge.Basis // Can be 0 882 msgPurged, assets := s.purgeMessage(mvalid) 883 allPurged = append(allPurged, msg) 884 allAssets = append(allAssets, assets...) 885 writeback = append(writeback, msgPurged) 886 } 887 888 // queue asset deletions in the background 889 s.assetDeleter.DeleteAssets(ctx, uid, convID, allAssets) 890 // queue search index update in the background 891 go func(ctx context.Context) { 892 err := s.G().Indexer.Remove(ctx, convID, allPurged) 893 if err != nil { 894 s.Debug(ctx, "Error removing from indexer: %+v", err) 895 } 896 }(globals.BackgroundChatCtx(ctx, s.G())) 897 898 de("deleting %v messages", len(writeback)) 899 if err = s.engine.WriteMessages(ctx, convID, uid, writeback); err != nil { 900 de("write messages failed: %v", err) 901 return nil, err 902 } 903 904 err = s.delhTracker.setDeletedUpto(ctx, convID, uid, expunge.Upto) 905 if err != nil { 906 de("failed to store delh track: %v", err) 907 } 908 909 return &expunge, nil 910 } 911 912 // clearUpthrough clears up to the given message ID, inclusive 913 func (s *Storage) clearUpthrough(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 914 upthrough chat1.MessageID) (err Error) { 915 var ierr error 916 defer s.Trace(ctx, &ierr, "clearUpthrough")() 917 defer func() { ierr = s.castInternalError(err) }() 918 key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG()) 919 if ierr != nil { 920 return MiscError{Msg: "unable to get secret key: " + ierr.Error()} 921 } 922 ctx, err = s.engine.Init(ctx, key, convID, uid) 923 if err != nil { 924 return err 925 } 926 927 var msgIDs []chat1.MessageID 928 for m := upthrough; m > 0; m-- { 929 msgIDs = append(msgIDs, m) 930 } 931 return s.engine.ClearMessages(ctx, convID, uid, msgIDs) 932 } 933 934 // ClearBefore clears all messages up to (but not including) the upto messageID 935 func (s *Storage) ClearBefore(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 936 upto chat1.MessageID) (err Error) { 937 var ierr error 938 defer s.Trace(ctx, &ierr, fmt.Sprintf("ClearBefore: convID: %s, uid: %s, msgID: %d", convID, uid, upto))() 939 defer func() { ierr = s.castInternalError(err) }() 940 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 941 defer lock.Release(ctx) 942 943 // Abort, we don't want to overflow uint (chat1.MessageID) 944 if upto == 0 { 945 return nil 946 } 947 return s.clearUpthrough(ctx, convID, uid, upto-1) 948 } 949 950 func (s *Storage) ClearAll(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID) (err Error) { 951 var ierr error 952 defer s.Trace(ctx, &ierr, "ClearAll")() 953 defer func() { ierr = s.castInternalError(err) }() 954 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 955 defer lock.Release(ctx) 956 maxMsgID, err := s.idtracker.getMaxMessageID(ctx, convID, uid) 957 if err != nil { 958 return err 959 } 960 return s.clearUpthrough(ctx, convID, uid, maxMsgID) 961 } 962 963 func (s *Storage) ResultCollectorFromQuery(ctx context.Context, query *chat1.GetThreadQuery, 964 pagination *chat1.Pagination) ResultCollector { 965 var num int 966 if pagination != nil { 967 num = pagination.Num 968 } else { 969 num = maxFetchNum 970 } 971 972 if query != nil && len(query.MessageTypes) > 0 { 973 s.Debug(ctx, "ResultCollectorFromQuery: types: %v", query.MessageTypes) 974 return NewTypedResultCollector(num, query.MessageTypes) 975 } 976 return NewSimpleResultCollector(num, false) 977 } 978 979 func (s *Storage) fetchUpToMsgIDLocked(ctx context.Context, rc ResultCollector, 980 convID chat1.ConversationID, uid gregor1.UID, msgID chat1.MessageID, query *chat1.GetThreadQuery, 981 pagination *chat1.Pagination) (res FetchResult, err Error) { 982 983 if err = isAbortedRequest(ctx); err != nil { 984 return res, err 985 } 986 // Fetch secret key 987 key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG()) 988 if ierr != nil { 989 return res, MiscError{Msg: "unable to get secret key: " + ierr.Error()} 990 } 991 992 // Init storage engine first 993 ctx, err = s.engine.Init(ctx, key, convID, uid) 994 if err != nil { 995 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 996 } 997 998 // Calculate seek parameters 999 var maxID, minID chat1.MessageID 1000 var num int 1001 if pagination == nil { 1002 maxID = msgID 1003 num = maxFetchNum 1004 } else { 1005 var pid chat1.MessageID 1006 num = pagination.Num 1007 if len(pagination.Next) == 0 && len(pagination.Previous) == 0 { 1008 maxID = msgID 1009 } else if len(pagination.Next) > 0 { 1010 if derr := decode(pagination.Next, &pid); derr != nil { 1011 err = RemoteError{Msg: "Fetch: failed to decode pager: " + derr.Error()} 1012 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 1013 } 1014 maxID = pid - 1 1015 minID = 0 1016 s.Debug(ctx, "Fetch: next pagination: pid: %d", pid) 1017 } else { 1018 if derr := decode(pagination.Previous, &pid); derr != nil { 1019 err = RemoteError{Msg: "Fetch: failed to decode pager: " + derr.Error()} 1020 return res, s.maybeNukeLocked(ctx, false, err, convID, uid) 1021 } 1022 maxID = chat1.MessageID(int(pid) + num) 1023 minID = pid 1024 s.Debug(ctx, "Fetch: prev pagination: pid: %d", pid) 1025 } 1026 } 1027 s.Debug(ctx, "Fetch: maxID: %d num: %d", maxID, num) 1028 1029 // Figure out how to determine we are done seeking (unless client tells us how to) 1030 if rc == nil { 1031 rc = s.ResultCollectorFromQuery(ctx, query, pagination) 1032 } 1033 s.Debug(ctx, "Fetch: using result collector: %s", rc) 1034 1035 // Run seek looking for all the messages 1036 if err = s.engine.ReadMessages(ctx, rc, convID, uid, maxID, minID); err != nil { 1037 return res, err 1038 } 1039 msgs := rc.Result() 1040 1041 // Clear out any ephemeral messages that have exploded before we hand these 1042 // messages out. 1043 explodedMsgs, err := s.explodeExpiredMessages(ctx, convID, uid, msgs) 1044 if err != nil { 1045 return res, err 1046 } 1047 res.Exploded = explodedMsgs 1048 1049 // Get the stored latest point upto which has been deleted. 1050 // `maxDeletedUpto` can be behind the times, so the pager is patched later in ConvSource. 1051 // It will be behind the times if a retention policy is the last expunger and only a full inbox sync has happened. 1052 var maxDeletedUpto chat1.MessageID 1053 delh, err := s.delhTracker.getEntry(ctx, convID, uid) 1054 switch err.(type) { 1055 case nil: 1056 maxDeletedUpto = delh.MaxDeleteHistoryUpto 1057 case MissError: 1058 default: 1059 return res, err 1060 } 1061 s.Debug(ctx, "Fetch: using max deleted upto: %v for pager", maxDeletedUpto) 1062 1063 // Form paged result 1064 var pmsgs []pager.Message 1065 for _, m := range msgs { 1066 pmsgs = append(pmsgs, m) 1067 } 1068 if res.Thread.Pagination, ierr = pager.NewThreadPager().MakePage(pmsgs, num, maxDeletedUpto); ierr != nil { 1069 return res, 1070 NewInternalError(ctx, s.DebugLabeler, "Fetch: failed to encode pager: %s", ierr.Error()) 1071 } 1072 res.Thread.Messages = msgs 1073 1074 s.Debug(ctx, "Fetch: cache hit: num: %d", len(msgs)) 1075 return res, nil 1076 } 1077 1078 func (s *Storage) FetchUpToLocalMaxMsgID(ctx context.Context, 1079 convID chat1.ConversationID, uid gregor1.UID, rc ResultCollector, iboxMaxMsgID chat1.MessageID, 1080 query *chat1.GetThreadQuery, pagination *chat1.Pagination) (res FetchResult, err Error) { 1081 var ierr error 1082 defer s.Trace(ctx, &ierr, "FetchUpToLocalMaxMsgID")() 1083 defer func() { ierr = s.castInternalError(err) }() 1084 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 1085 defer lock.Release(ctx) 1086 1087 maxMsgID, err := s.idtracker.getMaxMessageID(ctx, convID, uid) 1088 if err != nil { 1089 return res, err 1090 } 1091 if iboxMaxMsgID > maxMsgID { 1092 s.Debug(ctx, "FetchUpToLocalMaxMsgID: overriding locally stored max msgid with ibox: %d", 1093 iboxMaxMsgID) 1094 maxMsgID = iboxMaxMsgID 1095 } 1096 s.Debug(ctx, "FetchUpToLocalMaxMsgID: using max msgID: %d", maxMsgID) 1097 1098 return s.fetchUpToMsgIDLocked(ctx, rc, convID, uid, maxMsgID, query, pagination) 1099 } 1100 1101 func (s *Storage) Fetch(ctx context.Context, conv chat1.Conversation, 1102 uid gregor1.UID, rc ResultCollector, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (res FetchResult, err Error) { 1103 var ierr error 1104 defer s.Trace(ctx, &ierr, "Fetch")() 1105 defer func() { ierr = s.castInternalError(err) }() 1106 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), conv.GetConvID().String()) 1107 defer lock.Release(ctx) 1108 1109 return s.fetchUpToMsgIDLocked(ctx, rc, conv.GetConvID(), uid, conv.ReaderInfo.MaxMsgid, 1110 query, pagination) 1111 } 1112 1113 func (s *Storage) FetchMessages(ctx context.Context, convID chat1.ConversationID, 1114 uid gregor1.UID, msgIDs []chat1.MessageID) (res []*chat1.MessageUnboxed, err Error) { 1115 var ierr error 1116 defer s.Trace(ctx, &ierr, "FetchMessages")() 1117 defer func() { ierr = s.castInternalError(err) }() 1118 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 1119 defer lock.Release(ctx) 1120 if err = isAbortedRequest(ctx); err != nil { 1121 return res, err 1122 } 1123 // Fetch secret key 1124 key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG()) 1125 if ierr != nil { 1126 return nil, MiscError{Msg: "unable to get secret key: " + ierr.Error()} 1127 } 1128 1129 // Init storage engine first 1130 ctx, err = s.engine.Init(ctx, key, convID, uid) 1131 if err != nil { 1132 return nil, s.maybeNukeLocked(ctx, false, err, convID, uid) 1133 } 1134 1135 // Run seek looking for each message 1136 for _, msgID := range msgIDs { 1137 if msgID == 0 { 1138 res = append(res, nil) 1139 continue 1140 } 1141 msg, err := s.getMessage(ctx, convID, uid, msgID) 1142 if err != nil { 1143 return nil, s.maybeNukeLocked(ctx, false, err, convID, uid) 1144 } 1145 res = append(res, msg) 1146 } 1147 var msgs []chat1.MessageUnboxed 1148 // msgID -> index in res 1149 msgMap := make(map[chat1.MessageID]int) 1150 for i, m := range res { 1151 if m != nil { 1152 msg := *m 1153 msgs = append(msgs, msg) 1154 msgMap[msg.GetMessageID()] = i 1155 } 1156 } 1157 1158 _, err = s.explodeExpiredMessages(ctx, convID, uid, msgs) 1159 if err != nil { 1160 return nil, err 1161 } 1162 // write back any purged messages into our result. 1163 for _, m := range msgs { 1164 index, ok := msgMap[m.GetMessageID()] 1165 if !ok { 1166 s.Debug(ctx, "unable to find msg %d in msgMap", m.GetMessageID()) 1167 continue 1168 } 1169 msg := m 1170 res[index] = &msg 1171 } 1172 1173 return res, nil 1174 } 1175 1176 func (s *Storage) FetchUnreadlineID(ctx context.Context, convID chat1.ConversationID, 1177 uid gregor1.UID, readMsgID chat1.MessageID) (msgID *chat1.MessageID, err Error) { 1178 var ierr error 1179 defer s.Trace(ctx, &ierr, "FetchUnreadlineID")() 1180 defer func() { ierr = s.castInternalError(err) }() 1181 lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String()) 1182 defer lock.Release(ctx) 1183 if err = isAbortedRequest(ctx); err != nil { 1184 return nil, err 1185 } 1186 // Fetch secret key 1187 key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG()) 1188 if ierr != nil { 1189 return nil, MiscError{Msg: "unable to get secret key: " + ierr.Error()} 1190 } 1191 1192 // Init storage engine first 1193 ctx, err = s.engine.Init(ctx, key, convID, uid) 1194 if err != nil { 1195 return nil, s.maybeNukeLocked(ctx, false, err, convID, uid) 1196 } 1197 1198 // Run seek looking for each message 1199 for unreadlineID := readMsgID + 1; unreadlineID < readMsgID+1000; unreadlineID++ { 1200 msg, err := s.getMessage(ctx, convID, uid, unreadlineID) 1201 if err != nil { 1202 return nil, s.maybeNukeLocked(ctx, false, err, convID, uid) 1203 } 1204 // If we are missing any messages just abort. 1205 if msg == nil { 1206 return nil, nil 1207 } 1208 // return the first non-deleted visible message we have 1209 if msg.IsValidFull() && utils.IsVisibleChatMessageType(msg.GetMessageType()) { 1210 return &unreadlineID, nil 1211 } 1212 } 1213 1214 return nil, nil 1215 } 1216 1217 func (s *Storage) UpdateTLFIdentifyBreak(ctx context.Context, tlfID chat1.TLFID, 1218 breaks []keybase1.TLFIdentifyFailure) error { 1219 return s.breakTracker.UpdateTLF(ctx, tlfID, breaks) 1220 } 1221 1222 func (s *Storage) IsTLFIdentifyBroken(ctx context.Context, tlfID chat1.TLFID) bool { 1223 idBroken, err := s.breakTracker.IsTLFBroken(ctx, tlfID) 1224 if err != nil { 1225 s.Debug(ctx, "IsTLFIdentifyBroken: got error, so returning broken: %s", err.Error()) 1226 return true 1227 } 1228 return idBroken 1229 } 1230 1231 func (s *Storage) getMessage(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, msgID chat1.MessageID) (*chat1.MessageUnboxed, Error) { 1232 rc := NewSimpleResultCollector(1, true) 1233 if err := s.engine.ReadMessages(ctx, rc, convID, uid, msgID, 0); err != nil { 1234 // If we don't have the message, just keep going 1235 if _, ok := err.(MissError); ok { 1236 return nil, nil 1237 } 1238 return nil, err 1239 } 1240 res := rc.Result() 1241 if len(res) == 0 { 1242 return nil, nil 1243 } 1244 return &res[0], nil 1245 } 1246 1247 func (s *Storage) updateUnfurlTargetOnDelete(ctx context.Context, convID chat1.ConversationID, 1248 uid gregor1.UID, unfurlMsg chat1.MessageUnboxed) (res chat1.MessageUnboxed, err error) { 1249 defer s.Trace(ctx, &err, "updateUnfurlTargetOnDelete(%d)", 1250 unfurlMsg.GetMessageID())() 1251 if unfurlMsg.Valid().MessageBody.IsNil() { 1252 return unfurlMsg, errors.New("unfurl already deleted") 1253 } 1254 targetMsgID := unfurlMsg.Valid().MessageBody.Unfurl().MessageID 1255 targetMsg, err := s.getMessage(ctx, convID, uid, targetMsgID) 1256 if err != nil || targetMsg == nil { 1257 s.Debug(ctx, "updateUnfurlTargetOnDelete: no target message found: err: %s", err) 1258 return unfurlMsg, err 1259 } 1260 if !targetMsg.IsValid() { 1261 s.Debug(ctx, "updateUnfurlTargetOnDelete: target message is unvalid") 1262 return unfurlMsg, nil 1263 } 1264 mvalid := targetMsg.Valid() 1265 utils.RemoveUnfurl(&mvalid, unfurlMsg.GetMessageID()) 1266 return chat1.NewMessageUnboxedWithValid(mvalid), nil 1267 } 1268 1269 func (s *Storage) updateRepliesAffected(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 1270 replies []chat1.MessageID, replyMap map[chat1.MessageID]chat1.MessageUnboxed) { 1271 if len(replies) == 0 { 1272 return 1273 } 1274 defer s.Trace(ctx, nil, "updateRepliesAffected: num: %d", len(replies))() 1275 for _, reply := range replies { 1276 if _, ok := replyMap[reply]; ok { 1277 continue 1278 } 1279 replyMsg, err := s.getMessage(ctx, convID, uid, reply) 1280 if err != nil || replyMsg == nil { 1281 s.Debug(ctx, "updateRepliesAffected: failed to get message: err: %s", err) 1282 continue 1283 } 1284 replyMap[reply] = *replyMsg 1285 } 1286 } 1287 1288 func (s *Storage) GetExplodedReplies(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, 1289 exploded []chat1.MessageUnboxed) []chat1.MessageUnboxed { 1290 if len(exploded) == 0 { 1291 return nil 1292 } 1293 defer s.Trace(ctx, nil, "getExplodedReplies: num: %d", len(exploded))() 1294 var replies []chat1.MessageID 1295 for _, msg := range exploded { 1296 if !msg.IsValid() { 1297 continue 1298 } 1299 replies = append(replies, msg.Valid().ServerHeader.Replies...) 1300 } 1301 replyMap := make(map[chat1.MessageID]chat1.MessageUnboxed) 1302 s.updateRepliesAffected(ctx, convID, uid, replies, replyMap) 1303 return s.flatten(replyMap) 1304 } 1305 1306 // updateReactionIDs appends `msgid` to `reactionIDs` if it is not already 1307 // present. 1308 func (s *Storage) updateReactionIDs(reactionIDs []chat1.MessageID, msgid chat1.MessageID) ([]chat1.MessageID, bool) { 1309 for _, reactionID := range reactionIDs { 1310 if reactionID == msgid { 1311 return reactionIDs, false 1312 } 1313 } 1314 return append(reactionIDs, msgid), true 1315 } 1316 1317 // updateReactionTargetOnDelete modifies the reaction's target message when the 1318 // reaction itself is deleted 1319 func (s *Storage) updateReactionTargetOnDelete(ctx context.Context, convID chat1.ConversationID, 1320 uid gregor1.UID, reactionMsg *chat1.MessageUnboxed) (*chat1.MessageUnboxed, bool, Error) { 1321 s.Debug(ctx, "updateReactionTargetOnDelete: reationMsg: %v", reactionMsg) 1322 1323 if reactionMsg.Valid().MessageBody.IsNil() { 1324 return nil, false, nil 1325 } 1326 1327 targetMsgID := reactionMsg.Valid().MessageBody.Reaction().MessageID 1328 targetMsg, err := s.getMessage(ctx, convID, uid, targetMsgID) 1329 if err != nil || targetMsg == nil { 1330 return nil, false, err 1331 } 1332 if targetMsg.IsValid() { 1333 mvalid := targetMsg.Valid() 1334 reactionIDs := []chat1.MessageID{} 1335 for _, msgID := range mvalid.ServerHeader.ReactionIDs { 1336 if msgID != reactionMsg.GetMessageID() { 1337 reactionIDs = append(reactionIDs, msgID) 1338 } 1339 } 1340 updated := len(mvalid.ServerHeader.ReactionIDs) != len(reactionIDs) 1341 mvalid.ServerHeader.ReactionIDs = reactionIDs 1342 newMsg := chat1.NewMessageUnboxedWithValid(mvalid) 1343 return &newMsg, updated, nil 1344 } 1345 return nil, false, nil 1346 } 1347 1348 // Clears the body of a message and returns any assets to be deleted. 1349 func (s *Storage) purgeMessage(mvalid chat1.MessageUnboxedValid) (chat1.MessageUnboxed, []chat1.Asset) { 1350 assets := utils.AssetsForMessage(s.G(), mvalid.MessageBody) 1351 var emptyBody chat1.MessageBody 1352 mvalid.MessageBody = emptyBody 1353 var emptyReactions chat1.ReactionMap 1354 mvalid.Reactions = emptyReactions 1355 return chat1.NewMessageUnboxedWithValid(mvalid), assets 1356 }