github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/search/storage.go (about) 1 package search 2 3 import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha256" 7 "fmt" 8 "strings" 9 "sync" 10 11 lru "github.com/hashicorp/golang-lru" 12 "github.com/keybase/client/go/chat/globals" 13 "github.com/keybase/client/go/chat/storage" 14 "github.com/keybase/client/go/chat/utils" 15 "github.com/keybase/client/go/encrypteddb" 16 "github.com/keybase/client/go/libkb" 17 "github.com/keybase/client/go/protocol/chat1" 18 "github.com/keybase/client/go/protocol/gregor1" 19 ) 20 21 const ( 22 indexVersion = 16 23 tokenEntryVersion = 2 24 aliasEntryVersion = 3 25 26 mdDiskVersion = 4 27 tokenDiskVersion = 1 28 aliasDiskVersion = 1 29 ) 30 31 type tokenEntry struct { 32 Version string `codec:"v"` 33 MsgIDs map[chat1.MessageID]chat1.EmptyStruct `codec:"m"` 34 } 35 36 func newTokenEntry() *tokenEntry { 37 return &tokenEntry{ 38 Version: fmt.Sprintf("%d:%d", indexVersion, tokenEntryVersion), 39 MsgIDs: make(map[chat1.MessageID]chat1.EmptyStruct), 40 } 41 } 42 43 func (t *tokenEntry) dup() (res *tokenEntry) { 44 if t == nil { 45 return nil 46 } 47 res = new(tokenEntry) 48 res.Version = t.Version 49 res.MsgIDs = make(map[chat1.MessageID]chat1.EmptyStruct, len(t.MsgIDs)) 50 for m := range t.MsgIDs { 51 res.MsgIDs[m] = chat1.EmptyStruct{} 52 } 53 return res 54 } 55 56 var refTokenEntry = newTokenEntry() 57 58 type aliasEntry struct { 59 Version string `codec:"v"` 60 Aliases map[string]int `codec:"z"` 61 } 62 63 func newAliasEntry() *aliasEntry { 64 return &aliasEntry{ 65 Version: fmt.Sprintf("%d:%d", indexVersion, aliasEntryVersion), 66 Aliases: make(map[string]int), 67 } 68 } 69 70 func (a *aliasEntry) dup() (res *aliasEntry) { 71 if a == nil { 72 return nil 73 } 74 res = new(aliasEntry) 75 res.Version = a.Version 76 res.Aliases = make(map[string]int, len(a.Aliases)) 77 for k, v := range a.Aliases { 78 res.Aliases[k] = v 79 } 80 return res 81 } 82 83 func (a *aliasEntry) add(token string) { 84 a.Aliases[token]++ 85 } 86 87 func (a *aliasEntry) remove(token string) bool { 88 a.Aliases[token]-- 89 if a.Aliases[token] == 0 { 90 delete(a.Aliases, token) 91 return true 92 } 93 return false 94 } 95 96 var refAliasEntry = newAliasEntry() 97 98 type diskStorage interface { 99 GetTokenEntry(ctx context.Context, convID chat1.ConversationID, 100 token string) (res *tokenEntry, err error) 101 PutTokenEntry(ctx context.Context, convID chat1.ConversationID, 102 token string, te *tokenEntry) error 103 RemoveTokenEntry(ctx context.Context, convID chat1.ConversationID, token string) 104 GetAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error) 105 PutAliasEntry(ctx context.Context, alias string, ae *aliasEntry) error 106 RemoveAliasEntry(ctx context.Context, alias string) 107 GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error) 108 PutMetadata(ctx context.Context, convID chat1.ConversationID, md *indexMetadata) error 109 Flush() error 110 Cancel() 111 } 112 113 type tokenBatch struct { 114 convID chat1.ConversationID 115 tokens map[string]*tokenEntry 116 } 117 118 func newTokenBatch(convID chat1.ConversationID) *tokenBatch { 119 return &tokenBatch{ 120 convID: convID, 121 tokens: make(map[string]*tokenEntry), 122 } 123 } 124 125 type mdBatch struct { 126 convID chat1.ConversationID 127 md *indexMetadata 128 } 129 130 type batchingStore struct { 131 utils.DebugLabeler 132 sync.Mutex 133 134 uid gregor1.UID 135 mdb *libkb.JSONLocalDb 136 edb *encrypteddb.EncryptedDB 137 keyFn func(ctx context.Context) ([32]byte, error) 138 aliasBatch map[string]*aliasEntry 139 tokenBatch map[chat1.ConvIDStr]*tokenBatch 140 mdBatch map[chat1.ConvIDStr]*mdBatch 141 } 142 143 func newBatchingStore(g *globals.Context, uid gregor1.UID, 144 keyFn func(ctx context.Context) ([32]byte, error), edb *encrypteddb.EncryptedDB, 145 mdb *libkb.JSONLocalDb) *batchingStore { 146 b := &batchingStore{ 147 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Search.batchingStore", false), 148 uid: uid, 149 keyFn: keyFn, 150 edb: edb, 151 mdb: mdb, 152 } 153 b.Lock() 154 b.resetLocked() 155 b.Unlock() 156 return b 157 } 158 159 func (b *batchingStore) resetLocked() { 160 b.aliasBatch = make(map[string]*aliasEntry) 161 b.tokenBatch = make(map[chat1.ConvIDStr]*tokenBatch) 162 b.mdBatch = make(map[chat1.ConvIDStr]*mdBatch) 163 } 164 165 func (b *batchingStore) GetTokenEntry(ctx context.Context, convID chat1.ConversationID, 166 token string) (res *tokenEntry, err error) { 167 b.Lock() 168 defer b.Unlock() 169 batch, ok := b.tokenBatch[convID.ConvIDStr()] 170 if ok && batch.tokens[token] != nil { 171 return batch.tokens[token].dup(), nil 172 } 173 key, err := tokenKey(ctx, b.uid, convID, token, b.keyFn) 174 if err != nil { 175 return nil, err 176 } 177 res = new(tokenEntry) 178 found, err := b.edb.Get(ctx, key, res) 179 if err != nil { 180 return nil, err 181 } 182 if !found { 183 return nil, nil 184 } 185 return res, nil 186 } 187 188 func (b *batchingStore) PutTokenEntry(ctx context.Context, convID chat1.ConversationID, 189 token string, te *tokenEntry) (err error) { 190 b.Lock() 191 defer b.Unlock() 192 key := convID.ConvIDStr() 193 batch, ok := b.tokenBatch[key] 194 if !ok { 195 batch = newTokenBatch(convID) 196 } 197 batch.tokens[token] = te 198 b.tokenBatch[key] = batch 199 return nil 200 } 201 202 func (b *batchingStore) RemoveTokenEntry(ctx context.Context, convID chat1.ConversationID, 203 token string) { 204 b.Lock() 205 defer b.Unlock() 206 batch, ok := b.tokenBatch[convID.ConvIDStr()] 207 if ok { 208 delete(batch.tokens, token) 209 } 210 key, err := tokenKey(ctx, b.uid, convID, token, b.keyFn) 211 if err != nil { 212 b.Debug(ctx, "RemoveTokenEntry: failed to get tokenkey: %s", err) 213 return 214 } 215 if err := b.mdb.Delete(key); err != nil { 216 b.Debug(ctx, "RemoveTokenEntry: failed to delete key: %s", err) 217 } 218 } 219 220 func (b *batchingStore) GetAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error) { 221 b.Lock() 222 defer b.Unlock() 223 var ok bool 224 if res, ok = b.aliasBatch[alias]; ok { 225 return res.dup(), nil 226 } 227 key, err := aliasKey(ctx, alias, b.keyFn) 228 if err != nil { 229 return res, err 230 } 231 res = new(aliasEntry) 232 found, err := b.edb.Get(ctx, key, res) 233 if err != nil { 234 return nil, err 235 } 236 if !found { 237 return nil, nil 238 } 239 return res, nil 240 } 241 242 func (b *batchingStore) PutAliasEntry(ctx context.Context, alias string, ae *aliasEntry) (err error) { 243 b.Lock() 244 defer b.Unlock() 245 b.aliasBatch[alias] = ae 246 return nil 247 } 248 249 func (b *batchingStore) RemoveAliasEntry(ctx context.Context, alias string) { 250 b.Lock() 251 defer b.Unlock() 252 delete(b.aliasBatch, alias) 253 key, err := aliasKey(ctx, alias, b.keyFn) 254 if err != nil { 255 b.Debug(ctx, "RemoveAliasEntry: failed to get key: %s", err) 256 return 257 } 258 if err := b.mdb.Delete(key); err != nil { 259 b.Debug(ctx, "RemoveAliasEntry: failed to delete key: %s", err) 260 } 261 } 262 263 func (b *batchingStore) GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error) { 264 b.Lock() 265 defer b.Unlock() 266 if md, ok := b.mdBatch[convID.ConvIDStr()]; ok { 267 return md.md.dup(), nil 268 } 269 key := metadataKey(b.uid, convID) 270 res = new(indexMetadata) 271 found, err := b.mdb.GetIntoMsgpack(res, key) 272 if err != nil { 273 return nil, err 274 } 275 if !found { 276 return nil, nil 277 } 278 return res, nil 279 } 280 281 func (b *batchingStore) PutMetadata(ctx context.Context, convID chat1.ConversationID, md *indexMetadata) (err error) { 282 b.Lock() 283 defer b.Unlock() 284 b.mdBatch[convID.ConvIDStr()] = &mdBatch{ 285 md: md, 286 convID: convID, 287 } 288 return nil 289 } 290 291 func (b *batchingStore) Flush() (err error) { 292 ctx := context.Background() 293 defer b.Trace(ctx, &err, "Flush")() 294 b.Lock() 295 defer b.Unlock() 296 if len(b.tokenBatch) == 0 && len(b.aliasBatch) == 0 && len(b.mdBatch) == 0 { 297 return nil 298 } 299 defer b.resetLocked() 300 301 b.Debug(ctx, "Flush: flushing tokens from %d convs", len(b.tokenBatch)) 302 for _, tokenBatch := range b.tokenBatch { 303 b.Debug(ctx, "Flush: flushing %d tokens from %s", len(tokenBatch.tokens), tokenBatch.convID) 304 for token, te := range tokenBatch.tokens { 305 key, err := tokenKey(ctx, b.uid, tokenBatch.convID, token, b.keyFn) 306 if err != nil { 307 return err 308 } 309 if err := b.edb.Put(ctx, key, te); err != nil { 310 return err 311 } 312 } 313 } 314 b.Debug(ctx, "Flush: flushing %d aliases", len(b.aliasBatch)) 315 for alias, ae := range b.aliasBatch { 316 key, err := aliasKey(ctx, alias, b.keyFn) 317 if err != nil { 318 return err 319 } 320 if err := b.edb.Put(ctx, key, ae); err != nil { 321 return err 322 } 323 } 324 b.Debug(ctx, "Flush: flushing %d conv metadata", len(b.mdBatch)) 325 for _, mdBatch := range b.mdBatch { 326 b.Debug(ctx, "Flush: flushing md from %s", mdBatch.convID) 327 if err := b.mdb.PutObjMsgpack(metadataKey(b.uid, mdBatch.convID), nil, mdBatch.md); err != nil { 328 return err 329 } 330 } 331 return nil 332 } 333 334 func (b *batchingStore) Cancel() { 335 defer b.Trace(context.Background(), nil, "Cancel")() 336 b.Lock() 337 defer b.Unlock() 338 b.resetLocked() 339 } 340 341 type store struct { 342 globals.Contextified 343 utils.DebugLabeler 344 sync.RWMutex 345 346 uid gregor1.UID 347 keyFn func(ctx context.Context) ([32]byte, error) 348 aliasCache *lru.Cache 349 tokenCache *lru.Cache 350 diskStorage diskStorage 351 } 352 353 func newStore(g *globals.Context, uid gregor1.UID) *store { 354 ac, _ := lru.New(10000) 355 tc, _ := lru.New(3000) 356 keyFn := func(ctx context.Context) ([32]byte, error) { 357 return storage.GetSecretBoxKey(ctx, g.ExternalG()) 358 } 359 dbFn := func(g *libkb.GlobalContext) *libkb.JSONLocalDb { 360 return g.LocalChatDb 361 } 362 return &store{ 363 Contextified: globals.NewContextified(g), 364 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Search.store", false), 365 uid: uid, 366 keyFn: keyFn, 367 aliasCache: ac, 368 tokenCache: tc, 369 diskStorage: newBatchingStore(g, uid, keyFn, encrypteddb.New(g.ExternalG(), dbFn, keyFn), 370 g.LocalChatDb), 371 } 372 } 373 374 func metadataKey(uid gregor1.UID, convID chat1.ConversationID) libkb.DbKey { 375 return metadataKeyWithVersion(uid, convID, mdDiskVersion) 376 } 377 378 func metadataKeyWithVersion(uid gregor1.UID, convID chat1.ConversationID, version int) libkb.DbKey { 379 var key string 380 switch version { 381 case 1: 382 // original key 383 key = fmt.Sprintf("idx:%s:%s", convID, uid) 384 case 2: 385 // uid as a prefix makes more sense for leveldb to keep values 386 // co-located 387 key = fmt.Sprintf("idx:%s:%s", uid, convID) 388 case 3: 389 // changed to use chat1.ConversationIndexDisk to store arrays instead 390 // of maps. 391 key = fmt.Sprintf("idxd:%s:%s", uid, convID) 392 case 4: 393 // change to store metadata separate from tokens/aliases 394 key = fmt.Sprintf("md:%s:%s", uid, convID) 395 default: 396 panic("invalid index key version specified") 397 } 398 return libkb.DbKey{ 399 Typ: libkb.DBChatIndex, 400 Key: key, 401 } 402 } 403 404 func tokenKey(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, dat string, 405 keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) { 406 return tokenKeyWithVersion(ctx, uid, convID, dat, tokenDiskVersion, keyFn) 407 } 408 409 func tokenKeyWithVersion(ctx context.Context, uid gregor1.UID, 410 convID chat1.ConversationID, dat string, version int, keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) { 411 var key string 412 switch version { 413 case 1: 414 material, err := keyFn(ctx) 415 if err != nil { 416 return res, err 417 } 418 hasher := hmac.New(sha256.New, material[:]) 419 _, err = hasher.Write([]byte(dat)) 420 if err != nil { 421 return res, err 422 } 423 _, err = hasher.Write(convID.DbShortForm()) 424 if err != nil { 425 return res, err 426 } 427 _, err = hasher.Write(uid.Bytes()) 428 if err != nil { 429 return res, err 430 } 431 _, err = hasher.Write([]byte(libkb.EncryptionReasonChatIndexerTokenKey)) 432 if err != nil { 433 return res, err 434 } 435 key = fmt.Sprintf("tm:%s:%s:%s", uid, convID, hasher.Sum(nil)) 436 default: 437 return res, fmt.Errorf("unexpected token version %d", version) 438 } 439 return libkb.DbKey{ 440 Typ: libkb.DBChatIndex, 441 Key: key, 442 }, nil 443 } 444 445 func aliasKey(ctx context.Context, dat string, 446 keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) { 447 return aliasKeyWithVersion(ctx, dat, aliasDiskVersion, keyFn) 448 } 449 450 func aliasKeyWithVersion(ctx context.Context, dat string, version int, 451 keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) { 452 var key string 453 switch version { 454 case 1: 455 material, err := keyFn(ctx) 456 if err != nil { 457 return res, err 458 } 459 hasher := hmac.New(sha256.New, material[:]) 460 _, err = hasher.Write([]byte(dat)) 461 if err != nil { 462 return res, err 463 } 464 _, err = hasher.Write([]byte(libkb.EncryptionReasonChatIndexerAliasKey)) 465 if err != nil { 466 return res, err 467 } 468 key = fmt.Sprintf("al:%s", hasher.Sum(nil)) 469 default: 470 return res, fmt.Errorf("unexpected token version %d", version) 471 } 472 return libkb.DbKey{ 473 Typ: libkb.DBChatIndex, 474 Key: key, 475 }, nil 476 } 477 478 // deleteOldVersions purges old disk structures so we don't error out on msg 479 // pack decode or strand indexes with ephemeral content. 480 func (s *store) deleteOldVersions(ctx context.Context, keyFn func(int) (libkb.DbKey, error), minVersion, maxVersion int) { 481 for version := minVersion; version < maxVersion; version++ { 482 key, err := keyFn(version) 483 if err != nil { 484 s.Debug(ctx, "unable to get key for version %d, %v", version, err) 485 continue 486 } 487 if err := s.G().LocalChatDb.Delete(key); err != nil { 488 s.Debug(ctx, "deleteOldVersions: failed to delete key: %s", err) 489 } 490 } 491 } 492 493 func (s *store) deleteOldMetadataVersions(ctx context.Context, convID chat1.ConversationID) { 494 keyFn := func(version int) (libkb.DbKey, error) { 495 return metadataKeyWithVersion(s.uid, convID, version), nil 496 } 497 s.deleteOldVersions(ctx, keyFn, 3, mdDiskVersion) 498 } 499 500 func (s *store) deleteOldTokenVersions(ctx context.Context, convID chat1.ConversationID, token string) { 501 keyFn := func(version int) (libkb.DbKey, error) { 502 return tokenKeyWithVersion(ctx, s.uid, convID, token, version, s.keyFn) 503 } 504 s.deleteOldVersions(ctx, keyFn, 1, tokenDiskVersion) 505 } 506 507 func (s *store) deleteOldAliasVersions(ctx context.Context, alias string) { 508 keyFn := func(version int) (libkb.DbKey, error) { 509 return aliasKeyWithVersion(ctx, alias, version, s.keyFn) 510 } 511 s.deleteOldVersions(ctx, keyFn, 1, aliasDiskVersion) 512 } 513 514 func (s *store) GetHits(ctx context.Context, convID chat1.ConversationID, term string) (res map[chat1.MessageID]chat1.EmptyStruct, err error) { 515 defer s.Trace(ctx, &err, "GetHits")() 516 s.RLock() 517 defer s.RUnlock() 518 res = make(map[chat1.MessageID]chat1.EmptyStruct) 519 // Get all terms and aliases 520 terms := make(map[string]chat1.EmptyStruct) 521 ae, err := s.getAliasEntry(ctx, term) 522 if err != nil { 523 return res, err 524 } 525 aliases := ae.Aliases 526 terms[term] = chat1.EmptyStruct{} 527 for alias := range aliases { 528 terms[alias] = chat1.EmptyStruct{} 529 } 530 // Find all the msg IDs 531 for term := range terms { 532 te, err := s.getTokenEntry(ctx, convID, term) 533 if err != nil { 534 return nil, err 535 } 536 for msgID := range te.MsgIDs { 537 res[msgID] = chat1.EmptyStruct{} 538 } 539 } 540 return res, nil 541 } 542 543 func (s *store) tokenCacheKey(convID chat1.ConversationID, token string) string { 544 return fmt.Sprintf("%s:%s", convID, token) 545 } 546 547 func (s *store) getTokenEntry(ctx context.Context, convID chat1.ConversationID, token string) (res *tokenEntry, err error) { 548 cacheKey := s.tokenCacheKey(convID, token) 549 if te, ok := s.tokenCache.Get(cacheKey); ok { 550 return te.(*tokenEntry), nil 551 } 552 defer func() { 553 if err == nil { 554 s.tokenCache.Add(cacheKey, res.dup()) 555 } 556 }() 557 if res, err = s.diskStorage.GetTokenEntry(ctx, convID, token); err != nil { 558 return nil, err 559 } 560 if res == nil { 561 s.deleteOldTokenVersions(ctx, convID, token) 562 return newTokenEntry(), nil 563 } 564 if res.Version != refTokenEntry.Version { 565 return newTokenEntry(), nil 566 } 567 return res, nil 568 } 569 570 func (s *store) getAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error) { 571 if dat, ok := s.aliasCache.Get(alias); ok { 572 return dat.(*aliasEntry), nil 573 } 574 defer func() { 575 if err == nil { 576 s.aliasCache.Add(alias, res.dup()) 577 } 578 }() 579 if res, err = s.diskStorage.GetAliasEntry(ctx, alias); err != nil { 580 return nil, err 581 } 582 if res == nil { 583 s.deleteOldAliasVersions(ctx, alias) 584 return newAliasEntry(), nil 585 } 586 if res.Version != refAliasEntry.Version { 587 return newAliasEntry(), nil 588 } 589 return res, nil 590 } 591 592 func (s *store) putTokenEntry(ctx context.Context, convID chat1.ConversationID, 593 token string, te *tokenEntry) (err error) { 594 defer func() { 595 if err == nil { 596 s.tokenCache.Add(s.tokenCacheKey(convID, token), te.dup()) 597 } 598 }() 599 return s.diskStorage.PutTokenEntry(ctx, convID, token, te) 600 } 601 602 func (s *store) putAliasEntry(ctx context.Context, alias string, ae *aliasEntry) (err error) { 603 defer func() { 604 if err == nil { 605 s.aliasCache.Add(alias, ae.dup()) 606 } 607 }() 608 return s.diskStorage.PutAliasEntry(ctx, alias, ae) 609 } 610 611 func (s *store) deleteTokenEntry(ctx context.Context, convID chat1.ConversationID, 612 token string) { 613 s.tokenCache.Remove(s.tokenCacheKey(convID, token)) 614 s.diskStorage.RemoveTokenEntry(ctx, convID, token) 615 } 616 617 func (s *store) deleteAliasEntry(ctx context.Context, alias string) { 618 s.aliasCache.Remove(alias) 619 s.diskStorage.RemoveAliasEntry(ctx, alias) 620 } 621 622 // addTokens add the given tokens to the index under the given message 623 // id, when ingesting EDIT messages the msgID is of the superseded msg but the 624 // tokens are from the EDIT itself. 625 func (s *store) addTokens(ctx context.Context, 626 convID chat1.ConversationID, tokens tokenMap, msgID chat1.MessageID) error { 627 for token, aliases := range tokens { 628 // Update the token entry with the msg ID hit 629 te, err := s.getTokenEntry(ctx, convID, token) 630 if err != nil { 631 return err 632 } 633 te.MsgIDs[msgID] = chat1.EmptyStruct{} 634 635 // Update all the aliases to point at the token 636 for alias := range aliases { 637 aliasEntry, err := s.getAliasEntry(ctx, alias) 638 if err != nil { 639 return err 640 } 641 aliasEntry.add(token) 642 if err := s.putAliasEntry(ctx, alias, aliasEntry); err != nil { 643 return err 644 } 645 } 646 if err := s.putTokenEntry(ctx, convID, token, te); err != nil { 647 return err 648 } 649 } 650 return nil 651 } 652 653 func (s *store) addMsg(ctx context.Context, convID chat1.ConversationID, 654 msg chat1.MessageUnboxed) error { 655 tokens := tokensFromMsg(msg) 656 return s.addTokens(ctx, convID, tokens, msg.GetMessageID()) 657 } 658 659 func (s *store) removeMsg(ctx context.Context, convID chat1.ConversationID, 660 msg chat1.MessageUnboxed) error { 661 // find the msgID that the index stores 662 var msgID chat1.MessageID 663 switch msg.GetMessageType() { 664 case chat1.MessageType_EDIT, chat1.MessageType_ATTACHMENTUPLOADED: 665 superIDs, err := utils.GetSupersedes(msg) 666 if err != nil || len(superIDs) != 1 { 667 return err 668 } 669 msgID = superIDs[0] 670 default: 671 msgID = msg.GetMessageID() 672 } 673 674 for token, aliases := range tokensFromMsg(msg) { 675 // handle token 676 te, err := s.getTokenEntry(ctx, convID, token) 677 if err != nil { 678 return err 679 } 680 delete(te.MsgIDs, msgID) 681 if len(te.MsgIDs) == 0 { 682 s.deleteTokenEntry(ctx, convID, token) 683 } else { 684 // If there are still IDs, just write out the updated version 685 if err := s.putTokenEntry(ctx, convID, token, te); err != nil { 686 return err 687 } 688 } 689 // take out aliases 690 for alias := range aliases { 691 aliasEntry, err := s.getAliasEntry(ctx, alias) 692 if err != nil { 693 return err 694 } 695 if aliasEntry.remove(token) { 696 s.deleteAliasEntry(ctx, alias) 697 } else { 698 if err := s.putAliasEntry(ctx, alias, aliasEntry); err != nil { 699 return err 700 } 701 } 702 } 703 } 704 return nil 705 } 706 707 func (s *store) GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error) { 708 if res, err = s.diskStorage.GetMetadata(ctx, convID); err != nil { 709 return res, err 710 } 711 if res == nil { 712 s.deleteOldMetadataVersions(ctx, convID) 713 return newIndexMetadata(), nil 714 } 715 if res.Version != refIndexMetadata.Version { 716 return newIndexMetadata(), nil 717 } 718 return res, nil 719 } 720 721 func (s *store) Add(ctx context.Context, convID chat1.ConversationID, 722 msgs []chat1.MessageUnboxed) (err error) { 723 defer s.Trace(ctx, &err, "Add")() 724 s.Lock() 725 defer s.Unlock() 726 727 fetchSupersededMsgs := func(msg chat1.MessageUnboxed) []chat1.MessageUnboxed { 728 superIDs, err := utils.GetSupersedes(msg) 729 if err != nil { 730 s.Debug(ctx, "unable to get supersedes: %v", err) 731 return nil 732 } 733 reason := chat1.GetThreadReason_INDEXED_SEARCH 734 supersededMsgs, err := s.G().ChatHelper.GetMessages(ctx, s.uid, convID, superIDs, 735 false /* resolveSupersedes*/, &reason) 736 if err != nil { 737 // Log but ignore error 738 s.Debug(ctx, "unable to get fetch messages: %v", err) 739 return nil 740 } 741 return supersededMsgs 742 } 743 744 modified := false 745 md, err := s.GetMetadata(ctx, convID) 746 if err != nil { 747 s.Debug(ctx, "failed to get metadata: %s", err) 748 return err 749 } 750 defer func() { 751 if modified { 752 if err := s.diskStorage.PutMetadata(ctx, convID, md); err != nil { 753 s.Debug(ctx, "failed to put metadata: %s", err) 754 } 755 } 756 }() 757 for _, msg := range msgs { 758 seenIDs := md.SeenIDs 759 // Don't add if we've seen 760 if _, ok := seenIDs[msg.GetMessageID()]; ok { 761 continue 762 } 763 modified = true 764 seenIDs[msg.GetMessageID()] = chat1.EmptyStruct{} 765 // NOTE DELETE and DELETEHISTORY are handled through calls to `remove`, 766 // other messages will be added if there is any content that can be 767 // indexed. 768 switch msg.GetMessageType() { 769 case chat1.MessageType_ATTACHMENTUPLOADED: 770 supersededMsgs := fetchSupersededMsgs(msg) 771 for _, sm := range supersededMsgs { 772 seenIDs[sm.GetMessageID()] = chat1.EmptyStruct{} 773 err := s.addMsg(ctx, convID, sm) 774 if err != nil { 775 return err 776 } 777 } 778 case chat1.MessageType_EDIT: 779 tokens := tokensFromMsg(msg) 780 supersededMsgs := fetchSupersededMsgs(msg) 781 // remove the original message text and replace it with the edited 782 // contents (using the original id in the index) 783 for _, sm := range supersededMsgs { 784 seenIDs[sm.GetMessageID()] = chat1.EmptyStruct{} 785 err := s.removeMsg(ctx, convID, sm) 786 if err != nil { 787 return err 788 } 789 err = s.addTokens(ctx, convID, tokens, sm.GetMessageID()) 790 if err != nil { 791 return err 792 } 793 } 794 default: 795 err := s.addMsg(ctx, convID, msg) 796 if err != nil { 797 return err 798 } 799 } 800 } 801 return nil 802 } 803 804 // Remove tokenizes the message content and updates/removes index keys for each token. 805 func (s *store) Remove(ctx context.Context, convID chat1.ConversationID, 806 msgs []chat1.MessageUnboxed) (err error) { 807 defer s.Trace(ctx, &err, "Remove")() 808 s.Lock() 809 defer s.Unlock() 810 811 md, err := s.GetMetadata(ctx, convID) 812 if err != nil { 813 return err 814 } 815 816 modified := false 817 seenIDs := md.SeenIDs 818 for _, msg := range msgs { 819 // Don't remove if we haven't seen 820 if _, ok := seenIDs[msg.GetMessageID()]; !ok { 821 continue 822 } 823 modified = true 824 seenIDs[msg.GetMessageID()] = chat1.EmptyStruct{} 825 err := s.removeMsg(ctx, convID, msg) 826 if err != nil { 827 return err 828 } 829 } 830 if modified { 831 return s.diskStorage.PutMetadata(ctx, convID, md) 832 } 833 return nil 834 } 835 836 func (s *store) ClearMemory() { 837 defer s.Trace(context.Background(), nil, "ClearMemory")() 838 s.aliasCache.Purge() 839 s.tokenCache.Purge() 840 s.diskStorage.Cancel() 841 } 842 843 func (s *store) Clear(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) error { 844 mdKey := metadataKey(uid, convID) 845 tokKey := libkb.DbKey{ 846 Typ: libkb.DBChatIndex, 847 Key: fmt.Sprintf("tm:%s:%s:", uid, convID), 848 } 849 dbKeys, err := s.G().LocalChatDb.KeysWithPrefixes(mdKey.ToBytes(), tokKey.ToBytes()) 850 if err != nil { 851 return fmt.Errorf("could not get KeysWithPrefixes: %v", err) 852 } 853 epick := libkb.FirstErrorPicker{} 854 for dbKey := range dbKeys { 855 if dbKey.Typ == libkb.DBChatIndex && 856 (strings.HasPrefix(dbKey.Key, mdKey.Key) || 857 strings.HasPrefix(dbKey.Key, tokKey.Key)) { 858 epick.Push(s.G().LocalChatDb.Delete(dbKey)) 859 } 860 } 861 s.ClearMemory() 862 return epick.Error() 863 } 864 865 func (s *store) Flush() error { 866 return s.diskStorage.Flush() 867 }