github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/search/indexer.go (about) 1 // Copyright 2019 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package search 6 7 import ( 8 "context" 9 "os" 10 "path" 11 "path/filepath" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/blevesearch/bleve" 18 "github.com/blevesearch/bleve/index/store" 19 "github.com/blevesearch/bleve/registry" 20 "github.com/keybase/client/go/kbfs/data" 21 "github.com/keybase/client/go/kbfs/idutil" 22 "github.com/keybase/client/go/kbfs/ioutil" 23 "github.com/keybase/client/go/kbfs/kbfsmd" 24 "github.com/keybase/client/go/kbfs/kbfssync" 25 "github.com/keybase/client/go/kbfs/libcontext" 26 "github.com/keybase/client/go/kbfs/libfs" 27 "github.com/keybase/client/go/kbfs/libkbfs" 28 "github.com/keybase/client/go/kbfs/tlf" 29 kbname "github.com/keybase/client/go/kbun" 30 "github.com/keybase/client/go/logger" 31 "github.com/keybase/client/go/protocol/keybase1" 32 "github.com/pkg/errors" 33 "github.com/shirou/gopsutil/mem" 34 ldberrors "github.com/syndtr/goleveldb/leveldb/errors" 35 billy "gopkg.in/src-d/go-billy.v4" 36 ) 37 38 const ( 39 textFileType = "kbfsTextFile" 40 htmlFileType = "kbfsHTMLFile" 41 kvstoreNamePrefix = "kbfs" 42 bleveIndexType = "upside_down" 43 fsIndexStorageDir = "kbfs_index" 44 docDbDir = "docdb" 45 nameDocIDPrefix = "name_" 46 defaultIndexBatchSize = 10 * 1024 * 1024 // 10 MB 47 indexBatchSizeFactor = 500 48 minIndexBatchSize = 1 * 1024 * 1024 // 1 MB 49 maxIndexBatchSize = 100 * 1024 * 1024 // 100 MB 50 ) 51 52 const ( 53 // CtxOpID is the display name for the unique operation index ID tag. 54 ctxOpID = "IID" 55 ) 56 57 // CtxTagKey is the type used for unique context tags 58 type ctxTagKey int 59 60 const ( 61 // CtxIDKey is the type of the tag for unique operation IDs. 62 ctxIDKey ctxTagKey = iota 63 ) 64 65 type tlfMessage struct { 66 tlfID tlf.ID 67 rev kbfsmd.Revision 68 mode keybase1.FolderSyncMode 69 } 70 71 type initFn func( 72 context.Context, libkbfs.Config, idutil.SessionInfo, logger.Logger) ( 73 context.Context, libkbfs.Config, func(context.Context) error, error) 74 75 // Indexer can index and search KBFS TLFs. 76 type Indexer struct { 77 config libkbfs.Config 78 log logger.Logger 79 cancelLoop context.CancelFunc 80 remoteStatus libfs.RemoteStatus 81 configInitFn initFn 82 once sync.Once 83 indexWG kbfssync.RepeatedWaitGroup 84 loopWG kbfssync.RepeatedWaitGroup 85 kvstoreName string 86 fullIndexCB func() error // helpful for testing 87 progress *Progress 88 89 userChangedCh chan struct{} 90 tlfCh chan tlfMessage 91 shutdownCh chan struct{} 92 93 lock sync.RWMutex 94 index bleve.Index 95 indexConfig libkbfs.Config 96 configShutdown func(context.Context) error 97 blocksDb *IndexedBlockDb 98 tlfDb *IndexedTlfDb 99 docDb *DocDb 100 indexReadyCh chan struct{} 101 cancelCtx context.CancelFunc 102 fs billy.Filesystem 103 currBatch *bleve.Batch 104 currBatchSize uint64 105 batchFns []func() error 106 } 107 108 func newIndexerWithConfigInit(config libkbfs.Config, configInitFn initFn, 109 kvstoreName string) ( 110 *Indexer, error) { 111 log := config.MakeLogger("search") 112 i := &Indexer{ 113 config: config, 114 log: log, 115 configInitFn: configInitFn, 116 kvstoreName: kvstoreName, 117 progress: NewProgress(config.Clock()), 118 userChangedCh: make(chan struct{}, 1), 119 tlfCh: make(chan tlfMessage, 1000), 120 shutdownCh: make(chan struct{}), 121 indexReadyCh: make(chan struct{}), 122 } 123 124 i.startLoop() 125 return i, nil 126 } 127 128 // NewIndexer creates a new instance of an Indexer. 129 func NewIndexer(config libkbfs.Config) (*Indexer, error) { 130 return newIndexerWithConfigInit( 131 config, defaultInitConfig, kvstoreNamePrefix) 132 } 133 134 func (i *Indexer) startLoop() { 135 ctx, cancel := context.WithCancel(i.makeContext(context.Background())) 136 i.cancelLoop = cancel 137 i.loopWG.Add(1) 138 go i.loop(ctx) 139 } 140 141 func (i *Indexer) makeContext(ctx context.Context) context.Context { 142 return libkbfs.CtxWithRandomIDReplayable(ctx, ctxIDKey, ctxOpID, i.log) 143 } 144 145 func (i *Indexer) closeIndexLocked(ctx context.Context) error { 146 if i.index == nil { 147 return nil 148 } 149 150 err := i.index.Close() 151 if err != nil { 152 return err 153 } 154 155 // If the ready channel has already been closed, make a new one. 156 select { 157 case <-i.indexReadyCh: 158 i.indexReadyCh = make(chan struct{}) 159 default: 160 } 161 162 i.blocksDb.Shutdown(ctx) 163 i.tlfDb.Shutdown(ctx) 164 i.docDb.Shutdown(ctx) 165 166 shutdownErr := i.configShutdown(ctx) 167 i.index = nil 168 i.indexConfig = nil 169 i.blocksDb = nil 170 i.docDb = nil 171 i.tlfDb = nil 172 i.cancelCtx() 173 return shutdownErr 174 } 175 176 func defaultInitConfig( 177 ctx context.Context, config libkbfs.Config, session idutil.SessionInfo, 178 log logger.Logger) ( 179 newCtx context.Context, newConfig libkbfs.Config, 180 shutdownFn func(context.Context) error, err error) { 181 kbCtx := config.KbContext() 182 params, err := Params(kbCtx, config.StorageRoot(), session.UID) 183 if err != nil { 184 return nil, nil, nil, err 185 } 186 newCtx, newConfig, err = Init( 187 ctx, kbCtx, params, libkbfs.NewKeybaseServicePassthrough(config), 188 log, config.VLogLevel()) 189 if err != nil { 190 return nil, nil, nil, err 191 } 192 193 return newCtx, newConfig, newConfig.Shutdown, err 194 } 195 196 func (i *Indexer) loadIndex(ctx context.Context) (err error) { 197 i.log.CDebugf(ctx, "Loading index") 198 defer func() { i.log.CDebugf(ctx, "Done loading index: %+v", err) }() 199 i.lock.Lock() 200 defer i.lock.Unlock() 201 202 err = i.closeIndexLocked(ctx) 203 if err != nil { 204 return err 205 } 206 207 session, err := idutil.GetCurrentSessionIfPossible( 208 ctx, i.config.KBPKI(), true) 209 if err != nil { 210 return err 211 } 212 if session.Name == "" { 213 return nil 214 } 215 216 // Create a new Config object for the index data, with a storage 217 // root that's unique to this user. 218 ctx, indexConfig, configShutdown, err := i.configInitFn( 219 ctx, i.config, session, i.log) 220 if err != nil { 221 return err 222 } 223 cancelCtx := func() { 224 _ = libcontext.CleanupCancellationDelayer(ctx) 225 } 226 defer func() { 227 if err != nil { 228 configErr := indexConfig.Shutdown(ctx) 229 if configErr != nil { 230 i.log.CDebugf(ctx, "Couldn't shutdown config: %+v", configErr) 231 } 232 cancelCtx() 233 } 234 }() 235 236 // Store the index in a KBFS private folder for the current user, 237 // with all the blocks and MD stored in the storage root created 238 // above. Everything will be encrypted as if it were in the 239 // user's own private KBFS folder. 240 privateHandle, err := libkbfs.GetHandleFromFolderNameAndType( 241 ctx, indexConfig.KBPKI(), indexConfig.MDOps(), indexConfig, 242 string(session.Name), tlf.Private) 243 if err != nil { 244 return err 245 } 246 fs, err := libfs.NewFS( 247 ctx, indexConfig, privateHandle, data.MasterBranch, "", "", 0) 248 if err != nil { 249 return err 250 } 251 err = fs.MkdirAll(fsIndexStorageDir, 0400) 252 if err != nil { 253 return err 254 } 255 fs, err = fs.ChrootAsLibFS(fsIndexStorageDir) 256 if err != nil { 257 return err 258 } 259 260 // The index itself will use LevelDB storage that writes to the 261 // KBFS filesystem object made above. Register this storage type 262 // with Bleve. 263 i.fs = fs 264 i.once.Do(func() { 265 kvstoreConstructor := func( 266 mo store.MergeOperator, _ map[string]interface{}) ( 267 s store.KVStore, err error) { 268 return newBleveLevelDBStore(i.fs, false, mo) 269 } 270 registry.RegisterKVStore(i.kvstoreName, kvstoreConstructor) 271 }) 272 273 // Create the actual index using this storage type. Bleve has 274 // different calls for new vs. existing indicies, so we first need 275 // to check if it exists. The Bleve LevelDB storage takes a lock, 276 // so we don't really need to worry about concurrent KBFS 277 // processes here. 278 var index bleve.Index 279 p := filepath.Join(i.config.StorageRoot(), indexStorageDir, bleveIndexDir) 280 _, err = os.Stat(p) 281 switch { 282 case os.IsNotExist(errors.Cause(err)): 283 i.log.CDebugf(ctx, "Creating new index for user %s/%s", 284 session.Name, session.UID) 285 286 indexMapping, err := makeIndexMapping() 287 if err != nil { 288 return err 289 } 290 index, err = bleve.NewUsing( 291 p, indexMapping, bleveIndexType, i.kvstoreName, nil) 292 if err != nil { 293 return err 294 } 295 case err == nil: 296 i.log.CDebugf(ctx, "Using existing index for user %s/%s", 297 session.Name, session.UID) 298 299 index, err = bleve.OpenUsing(p, nil) 300 if err != nil { 301 return err 302 } 303 default: 304 return err 305 } 306 307 // Load the blocks DB. 308 blocksDb, err := newIndexedBlockDb(i.config, indexConfig.StorageRoot()) 309 if err != nil { 310 return err 311 } 312 313 // Load the TLF DB. 314 tlfDb, err := newIndexedTlfDb(i.config, indexConfig.StorageRoot()) 315 if err != nil { 316 return err 317 } 318 319 err = fs.MkdirAll(docDbDir, 0600) 320 if err != nil { 321 return err 322 } 323 docFS, err := fs.Chroot(docDbDir) 324 if err != nil { 325 return err 326 } 327 docDb, err := newDocDb(indexConfig, docFS) 328 if err != nil { 329 return err 330 } 331 332 err = indexConfig.KBFSOps().SyncFromServer( 333 ctx, fs.RootNode().GetFolderBranch(), nil) 334 if err != nil { 335 return err 336 } 337 338 i.index = index 339 i.indexConfig = indexConfig 340 i.configShutdown = configShutdown 341 i.blocksDb = blocksDb 342 i.tlfDb = tlfDb 343 i.docDb = docDb 344 i.cancelCtx = cancelCtx 345 close(i.indexReadyCh) 346 return nil 347 } 348 349 // UserChanged implements the libfs.RemoteStatusUpdater for Indexer. 350 func (i *Indexer) UserChanged( 351 ctx context.Context, oldName, newName kbname.NormalizedUsername) { 352 select { 353 case i.userChangedCh <- struct{}{}: 354 default: 355 i.log.CDebugf(ctx, "Dropping user changed notification") 356 } 357 } 358 359 var _ libfs.RemoteStatusUpdater = (*Indexer)(nil) 360 361 func (i *Indexer) getMDForRev( 362 ctx context.Context, tlfID tlf.ID, rev kbfsmd.Revision) ( 363 md libkbfs.ImmutableRootMetadata, err error) { 364 return libkbfs.GetSingleMD( 365 ctx, i.config, tlfID, kbfsmd.NullBranchID, rev, kbfsmd.Merged, nil) 366 } 367 368 func (i *Indexer) tlfQueueForProgress( 369 ctx context.Context, tlfID tlf.ID, rev kbfsmd.Revision) error { 370 md, err := i.getMDForRev(ctx, tlfID, rev) 371 if err != nil { 372 return err 373 } 374 // For now assume we will be indexing the entire TLF. If when 375 // we actually start indexing, we figure out that this is an 376 // incremental index, we can update it. `DiskUsage` is the 377 // encoded, padded size, but it's the best we can easily do 378 // right now. 379 i.progress.tlfQueue(tlfID, md.DiskUsage()) 380 return nil 381 } 382 383 // FullSyncStarted implements the libkbfs.SyncedTlfObserver interface 384 // for Indexer. 385 func (i *Indexer) FullSyncStarted( 386 ctx context.Context, tlfID tlf.ID, rev kbfsmd.Revision, 387 waitCh <-chan struct{}) { 388 i.log.CDebugf(ctx, "Sync started for %s/%d", tlfID, rev) 389 i.indexWG.Add(1) 390 go func() { 391 select { 392 case <-waitCh: 393 case <-i.shutdownCh: 394 i.indexWG.Done() 395 return 396 } 397 398 ctx := i.makeContext(context.Background()) 399 err := i.tlfQueueForProgress(ctx, tlfID, rev) 400 if err != nil { 401 i.log.CDebugf( 402 ctx, "Couldn't enqueue for %s/%s: %+v", tlfID, rev, err) 403 i.indexWG.Done() 404 return 405 } 406 407 m := tlfMessage{tlfID, rev, keybase1.FolderSyncMode_ENABLED} 408 select { 409 case i.tlfCh <- m: 410 default: 411 i.progress.tlfUnqueue(tlfID) 412 i.indexWG.Done() 413 i.log.CDebugf( 414 context.Background(), "Couldn't send TLF message for %s/%d", 415 tlfID, rev) 416 } 417 }() 418 } 419 420 // SyncModeChanged implements the libkbfs.SyncedTlfObserver interface 421 // for Indexer. 422 func (i *Indexer) SyncModeChanged( 423 ctx context.Context, tlfID tlf.ID, newMode keybase1.FolderSyncMode) { 424 i.log.CDebugf(ctx, "Sync mode changed for %s to %s", tlfID, newMode) 425 i.indexWG.Add(1) 426 427 // Don't enqueue progress for a TLF when the sync mode changes; if 428 // the TLF is now being synced, `FullSyncStarted` will also be 429 // called. 430 431 m := tlfMessage{tlfID, kbfsmd.RevisionUninitialized, newMode} 432 select { 433 case i.tlfCh <- m: 434 default: 435 i.indexWG.Done() 436 i.log.CDebugf( 437 context.Background(), "Couldn't send TLF message for %s/%s", 438 tlfID, newMode) 439 } 440 } 441 442 var _ libkbfs.SyncedTlfObserver = (*Indexer)(nil) 443 444 func (i *Indexer) getCurrentPtrAndNode( 445 ctx context.Context, parentNode libkbfs.Node, 446 childName data.PathPartString) ( 447 ptr data.BlockPointer, n libkbfs.Node, ei data.EntryInfo, err error) { 448 n, ei, err = i.config.KBFSOps().Lookup(ctx, parentNode, childName) 449 if err != nil { 450 return data.ZeroPtr, nil, data.EntryInfo{}, err 451 } 452 453 // Symlinks don't have block pointers. 454 if n == nil { 455 return data.ZeroPtr, nil, ei, nil 456 } 457 458 // Let's find the current block ID. 459 md, err := i.config.KBFSOps().GetNodeMetadata(ctx, n) 460 if err != nil { 461 return data.ZeroPtr, nil, data.EntryInfo{}, err 462 } 463 return md.BlockInfo.BlockPointer, n, ei, nil 464 } 465 466 func nameDocID(docID string) string { 467 return nameDocIDPrefix + docID 468 } 469 470 func (i *Indexer) flushBatchLocked(ctx context.Context) error { 471 if i.currBatch == nil { 472 return nil 473 } 474 defer func() { 475 i.currBatch = nil 476 i.batchFns = nil 477 }() 478 479 // Flush the old batch. 480 i.log.CDebugf( 481 ctx, "Flushing a batch of size %d", i.currBatch.TotalDocsSize()) 482 err := i.index.Batch(i.currBatch) 483 if err != nil { 484 return err 485 } 486 for _, f := range i.batchFns { 487 err := f() 488 if err != nil { 489 return err 490 } 491 } 492 return i.blocksDb.ClearMemory() 493 } 494 495 func (i *Indexer) flushBatch(ctx context.Context) error { 496 i.lock.Lock() 497 defer i.lock.Unlock() 498 return i.flushBatchLocked(ctx) 499 } 500 501 func (i *Indexer) refreshBatchLocked(ctx context.Context) error { 502 if i.index == nil { 503 return errors.New("Index not loaded") 504 } 505 err := i.flushBatchLocked(ctx) 506 if err != nil { 507 return err 508 } 509 i.currBatch = i.index.NewBatch() 510 511 // Try to scale the batch size appropriately, given the current 512 // available memory on the system. 513 i.currBatchSize = defaultIndexBatchSize 514 vmstat, err := mem.VirtualMemory() 515 if err == nil { 516 // Allow large batches only if there is plenty of available 517 // memory. Bleve allocates a lot of memory per batch (I think 518 // maybe 100x+ the batch size), so we need lots of spare 519 // overhead. 520 allowable := vmstat.Available / indexBatchSizeFactor 521 if allowable > maxIndexBatchSize { 522 allowable = maxIndexBatchSize 523 } else if allowable < minIndexBatchSize { 524 allowable = minIndexBatchSize 525 } 526 527 i.log.CDebugf( 528 ctx, "Setting the indexing batch size to %d "+ 529 "(available mem = %d)", allowable, vmstat.Available) 530 i.currBatchSize = allowable 531 } 532 533 return nil 534 } 535 536 func (i *Indexer) refreshBatch(ctx context.Context) error { 537 i.lock.Lock() 538 defer i.lock.Unlock() 539 return i.refreshBatchLocked(ctx) 540 } 541 542 func (i *Indexer) currBatchLocked(ctx context.Context) (*bleve.Batch, error) { 543 if i.currBatch == nil { 544 return nil, errors.New("No current batch") 545 } 546 547 if i.currBatch.TotalDocsSize() > i.currBatchSize { 548 err := i.refreshBatchLocked(ctx) 549 if err != nil { 550 return nil, err 551 } 552 } 553 return i.currBatch, nil 554 } 555 556 func (i *Indexer) checkDone(ctx context.Context) error { 557 select { 558 case <-ctx.Done(): 559 return ctx.Err() 560 case <-i.shutdownCh: 561 return errors.New("Shutdown") 562 default: 563 return nil 564 } 565 } 566 567 func (i *Indexer) indexChildWithPtrAndNode( 568 ctx context.Context, parentNode libkbfs.Node, parentDocID string, 569 childName data.PathPartString, oldPtr, newPtr data.BlockPointer, 570 n libkbfs.Node, ei data.EntryInfo, nextDocID string, 571 revision kbfsmd.Revision) (dirDoneFn func() error, err error) { 572 if i.blocksDb == nil { 573 return nil, errors.New("No indexed blocks db") 574 } 575 576 if i.fullIndexCB != nil { 577 // Error on indexing this node if the callback tells us to 578 // (useful for testing). 579 err := i.fullIndexCB() 580 if err != nil { 581 i.log.CDebugf(ctx, "Stopping index due to testing error: %+v", err) 582 return nil, err 583 } 584 } 585 586 err = i.checkDone(ctx) 587 if err != nil { 588 return nil, err 589 } 590 591 defer func() { 592 if err == nil { 593 // Mark the bytes of this child as indexed. This is the 594 // actual unencrypted size of the entry, which won't match 595 // up perfectly with the disk usage, but it's the easiest 596 // thing to do for now. 597 i.progress.indexedBytes(ei.Size) 598 } 599 }() 600 601 tlfID := n.GetFolderBranch().Tlf 602 603 // If the new pointer has already been indexed, skip indexing it again. 604 v, docID, dirDone, err := i.blocksDb.Get(ctx, newPtr) 605 switch errors.Cause(err) { 606 case nil: 607 if v == currentIndexedBlocksDbVersion { 608 i.log.CDebugf( 609 ctx, "%s/%s already indexed; skipping (type=%s, dirDone=%t)", 610 newPtr, childName, ei.Type, dirDone) 611 if ei.Type != data.Dir || dirDone { 612 return nil, nil 613 } 614 return func() error { 615 flushFn, err := i.blocksDb.PutMemory( 616 ctx, tlfID, newPtr, currentIndexedBlocksDbVersion, docID, 617 true) 618 if err != nil { 619 return err 620 } 621 i.lock.Lock() 622 defer i.lock.Unlock() 623 i.batchFns = append(i.batchFns, flushFn) 624 return nil 625 }, nil 626 } 627 case ldberrors.ErrNotFound: 628 default: 629 return nil, err 630 } 631 632 if oldPtr != data.ZeroPtr { 633 _, docID, _, err = i.blocksDb.Get(ctx, oldPtr) 634 switch errors.Cause(err) { 635 case nil: 636 case ldberrors.ErrNotFound: 637 return nil, errors.WithStack(OldPtrNotFound{oldPtr}) 638 default: 639 return nil, err 640 } 641 } else { 642 docID = nextDocID 643 } 644 645 dirDoneFn = func() error { 646 flushFn, err := i.blocksDb.PutMemory( 647 ctx, tlfID, newPtr, currentIndexedBlocksDbVersion, docID, true) 648 if err != nil { 649 return err 650 } 651 i.lock.Lock() 652 defer i.lock.Unlock() 653 i.batchFns = append(i.batchFns, flushFn) 654 return nil 655 } 656 657 // Get the content type and create a document based on that type. 658 d, nameD, err := makeDoc( 659 ctx, i.config, n, ei, revision, time.Unix(0, ei.Mtime)) 660 if err != nil { 661 return nil, err 662 } 663 664 i.lock.Lock() 665 defer i.lock.Unlock() 666 if i.index == nil { 667 return nil, errors.New("Index not loaded") 668 } 669 670 b, err := i.currBatchLocked(ctx) 671 if err != nil { 672 return nil, err 673 } 674 675 if d != nil { 676 err = b.Index(docID, d) 677 if err != nil { 678 return nil, err 679 } 680 } 681 err = b.Index(nameDocID(docID), nameD) 682 if err != nil { 683 return nil, err 684 } 685 686 // Put the docID into the DB after a successful indexing. 687 flushFn, err := i.blocksDb.PutMemory( 688 ctx, tlfID, newPtr, currentIndexedBlocksDbVersion, docID, false) 689 if err != nil { 690 return nil, err 691 } 692 693 i.batchFns = append(i.batchFns, func() error { 694 err := flushFn() 695 if err != nil { 696 return err 697 } 698 699 // Save the docID -> parentDocID mapping. 700 err = i.docDb.Put(ctx, docID, parentDocID, childName.Plaintext()) 701 if err != nil { 702 return err 703 } 704 705 // Delete the old pointer if one was given. 706 if oldPtr != data.ZeroPtr { 707 err = i.blocksDb.Delete(ctx, tlfID, oldPtr) 708 if err != nil { 709 return err 710 } 711 } 712 713 return nil 714 }) 715 716 return dirDoneFn, nil 717 } 718 719 func (i *Indexer) indexChild( 720 ctx context.Context, parentNode libkbfs.Node, parentDocID string, 721 childName data.PathPartString, nextDocID string, 722 revision kbfsmd.Revision) (dirDoneFn func() error, err error) { 723 ptr, n, ei, err := i.getCurrentPtrAndNode(ctx, parentNode, childName) 724 if err != nil { 725 return nil, err 726 } 727 728 if ptr == data.ZeroPtr { 729 // Skip indexing symlinks for now -- they are hard to track 730 // since they don't have a BlockPointer to put in the blocksDb. 731 return nil, nil 732 } 733 734 return i.indexChildWithPtrAndNode( 735 ctx, parentNode, parentDocID, childName, data.ZeroPtr, ptr, n, ei, 736 nextDocID, revision) 737 } 738 739 func (i *Indexer) updateChild( 740 ctx context.Context, parentNode libkbfs.Node, parentDocID string, 741 childName data.PathPartString, oldPtr data.BlockPointer, 742 revision kbfsmd.Revision) (dirDoneFn func() error, err error) { 743 newPtr, n, ei, err := i.getCurrentPtrAndNode(ctx, parentNode, childName) 744 if err != nil { 745 return nil, err 746 } 747 748 if newPtr == data.ZeroPtr { 749 // Symlinks should never be updated. 750 return nil, errors.Errorf("Symlink %s should not be updated", childName) 751 } 752 753 return i.indexChildWithPtrAndNode( 754 ctx, parentNode, parentDocID, childName, oldPtr, newPtr, n, ei, 755 "" /* should get picked up from DB, not from this param*/, revision) 756 } 757 758 func (i *Indexer) renameChild( 759 ctx context.Context, parentNode libkbfs.Node, parentDocID string, 760 childName data.PathPartString, revision kbfsmd.Revision) (err error) { 761 ptr, n, ei, err := i.getCurrentPtrAndNode(ctx, parentNode, childName) 762 if err != nil { 763 return err 764 } 765 766 if ptr == data.ZeroPtr { 767 // Ignore symlink renames. 768 return nil 769 } 770 771 i.log.CDebugf(ctx, "Found %s for child %s", ptr, childName) 772 773 if i.blocksDb == nil { 774 return errors.New("No indexed blocks db") 775 } 776 777 // Get the docID. 778 _, docID, _, err := i.blocksDb.Get(ctx, ptr) 779 if err != nil { 780 // Treat "not found" errors as real errors, since a rename 781 // implies that the doc should have already been indexed. 782 return err 783 } 784 785 i.lock.Lock() 786 defer i.lock.Unlock() 787 788 b, err := i.currBatchLocked(ctx) 789 if err != nil { 790 return err 791 } 792 793 newNameDoc := makeNameDoc(n, revision, time.Unix(0, ei.Mtime)) 794 err = b.Index(nameDocID(docID), newNameDoc) 795 if err != nil { 796 return err 797 } 798 799 // Rename the doc ID for the new name. 800 i.batchFns = append( 801 i.batchFns, 802 func() error { 803 // Fix the child name in the doc db. 804 return i.docDb.Put(ctx, docID, parentDocID, childName.Plaintext()) 805 }) 806 807 return nil 808 } 809 810 func (i *Indexer) deleteFromUnrefs( 811 ctx context.Context, tlfID tlf.ID, unrefs []data.BlockPointer) (err error) { 812 if i.blocksDb == nil { 813 return errors.New("No indexed blocks db") 814 } 815 816 // Find the right doc ID. 817 var docID string 818 var unref data.BlockPointer 819 unrefLoop: 820 for _, unref = range unrefs { 821 _, docID, _, err = i.blocksDb.Get(ctx, unref) 822 switch errors.Cause(err) { 823 case nil: 824 break unrefLoop 825 case ldberrors.ErrNotFound: 826 continue 827 default: 828 return err 829 } 830 } 831 if docID == "" { 832 i.log.CDebugf(ctx, "Couldn't find doc ID for deleted ptrs %v", unrefs) 833 return nil 834 } 835 836 i.lock.Lock() 837 defer i.lock.Unlock() 838 839 b, err := i.currBatchLocked(ctx) 840 if err != nil { 841 return err 842 } 843 844 b.Delete(docID) 845 b.Delete(nameDocID(docID)) 846 err = i.index.Batch(b) 847 if err != nil { 848 return err 849 } 850 851 i.batchFns = append( 852 i.batchFns, 853 func() error { return i.docDb.Delete(ctx, docID) }, 854 func() error { return i.blocksDb.Delete(ctx, tlfID, unref) }, 855 ) 856 return nil 857 } 858 859 func (i *Indexer) fsForRev( 860 ctx context.Context, tlfID tlf.ID, rev kbfsmd.Revision) (*libfs.FS, error) { 861 if rev == kbfsmd.RevisionUninitialized { 862 return nil, errors.New("No revision provided") 863 } 864 branch := data.MakeRevBranchName(rev) 865 866 md, err := i.getMDForRev(ctx, tlfID, rev) 867 if err != nil { 868 return nil, err 869 } 870 871 h := md.GetTlfHandle() 872 return libfs.NewReadonlyFS( 873 ctx, i.config, h, branch, "", "", keybase1.MDPriorityNormal) 874 } 875 876 func (i *Indexer) indexNewlySyncedTlfDir( 877 ctx context.Context, dir libkbfs.Node, 878 dirDocID string, rev kbfsmd.Revision) error { 879 err := i.checkDone(ctx) 880 if err != nil { 881 return err 882 } 883 884 children, err := i.config.KBFSOps().GetDirChildren(ctx, dir) 885 if err != nil { 886 return err 887 } 888 889 if len(children) == 0 { 890 // Nothing to do. 891 return nil 892 } 893 894 ids, err := i.blocksDb.GetNextDocIDs(len(children)) 895 if err != nil { 896 return err 897 } 898 899 currDocID := 0 900 for name, child := range children { 901 dirDoneFn, err := i.indexChild( 902 ctx, dir, dirDocID, name, ids[currDocID], rev) 903 if err != nil { 904 return err 905 } 906 docID := ids[currDocID] 907 currDocID++ 908 909 if child.Type == data.Dir && dirDoneFn != nil { 910 n, _, err := i.config.KBFSOps().Lookup(ctx, dir, name) 911 if err != nil { 912 return err 913 } 914 915 err = i.indexNewlySyncedTlfDir(ctx, n, docID, rev) 916 if err != nil { 917 return err 918 } 919 920 err = dirDoneFn() 921 if err != nil { 922 return err 923 } 924 } 925 } 926 return nil 927 } 928 929 func (i *Indexer) recordUpdatedNodePtr( 930 ctx context.Context, node libkbfs.Node, rev kbfsmd.Revision, docID string, 931 oldPtr data.BlockPointer) (dirDoneFn func() error, err error) { 932 md, err := i.config.KBFSOps().GetNodeMetadata(ctx, node) 933 if err != nil { 934 return nil, err 935 } 936 tlfID := node.GetFolderBranch().Tlf 937 i.lock.Lock() 938 defer i.lock.Unlock() 939 flushFn, err := i.blocksDb.PutMemory( 940 ctx, tlfID, md.BlockInfo.BlockPointer, 941 currentIndexedBlocksDbVersion, docID, false) 942 if err != nil { 943 return nil, err 944 } 945 i.batchFns = append(i.batchFns, flushFn) 946 947 return func() error { 948 flushFn, err := i.blocksDb.PutMemory( 949 ctx, tlfID, md.BlockInfo.BlockPointer, 950 currentIndexedBlocksDbVersion, docID, true) 951 if err != nil { 952 return err 953 } 954 955 i.lock.Lock() 956 defer i.lock.Unlock() 957 i.batchFns = append(i.batchFns, flushFn) 958 959 if oldPtr != data.ZeroPtr { 960 err := i.blocksDb.Delete(ctx, tlfID, oldPtr) 961 if err != nil { 962 return err 963 } 964 } 965 return nil 966 }, nil 967 } 968 969 func (i *Indexer) indexNewlySyncedTlf( 970 ctx context.Context, fs *libfs.FS, rev kbfsmd.Revision) (err error) { 971 root := fs.RootNode() 972 973 ids, err := i.blocksDb.GetNextDocIDs(1) 974 if err != nil { 975 return err 976 } 977 id := ids[0] 978 err = i.docDb.Put(ctx, id, "", fs.Handle().GetCanonicalPath()) 979 if err != nil { 980 return err 981 } 982 983 err = i.refreshBatch(ctx) 984 if err != nil { 985 return err 986 } 987 988 defer func() { 989 flushErr := i.flushBatch(ctx) 990 if flushErr == nil { 991 return 992 } 993 i.log.CDebugf(ctx, "Error flushing batch: %+v", flushErr) 994 if err == nil { 995 err = flushErr 996 } 997 }() 998 999 // Record the docID for the root node. But no need to index the 1000 // root dir, since it doesn't really have a name. 1001 dirDoneFn, err := i.recordUpdatedNodePtr(ctx, root, rev, id, data.ZeroPtr) 1002 if err != nil { 1003 return err 1004 } 1005 defer func() { 1006 if err != nil { 1007 return 1008 } 1009 1010 err = dirDoneFn() 1011 }() 1012 1013 return i.indexNewlySyncedTlfDir(ctx, root, id, rev) 1014 } 1015 1016 func (i *Indexer) doFullIndex( 1017 ctx context.Context, m tlfMessage, rev kbfsmd.Revision) (err error) { 1018 i.log.CDebugf(ctx, "Doing full index of %s at rev %d", m.tlfID, rev) 1019 defer func() { 1020 i.log.CDebugf( 1021 ctx, "Finished full index of %s at rev %d: %+v", m.tlfID, rev, err) 1022 }() 1023 1024 md, err := i.getMDForRev(ctx, m.tlfID, rev) 1025 if err != nil { 1026 return err 1027 } 1028 err = i.progress.startIndex(m.tlfID, md.DiskUsage(), indexFull) 1029 if err != nil { 1030 return err 1031 } 1032 defer func() { 1033 progErr := i.progress.finishIndex(m.tlfID) 1034 if progErr != nil { 1035 i.log.CDebugf(ctx, "Couldn't finish index: %+v", err) 1036 } 1037 }() 1038 1039 fs, err := i.fsForRev(ctx, m.tlfID, rev) 1040 if err != nil { 1041 return err 1042 } 1043 1044 // Check whether this revision has been garbage-collected yet. If 1045 // so, return a typed error. The caller may wish to clear out the 1046 // current index for the TLF in this case. 1047 status, _, err := i.config.KBFSOps().FolderStatus( 1048 ctx, fs.RootNode().GetFolderBranch()) 1049 if err != nil { 1050 return err 1051 } 1052 if rev <= status.LastGCRevision { 1053 return errors.WithStack( 1054 RevisionGCdError{m.tlfID, rev, status.LastGCRevision}) 1055 } 1056 1057 // Record that we've started a full sync for this TLF at this 1058 // revision. If it gets interrupted, it should be resumed on the 1059 // next restart of the indexer. There is no `indexedRev`, because 1060 // this function should only be called when a full index is 1061 // needed. 1062 err = i.tlfDb.Put(ctx, m.tlfID, kbfsmd.RevisionUninitialized, rev) 1063 if err != nil { 1064 return err 1065 } 1066 1067 defer func() { 1068 if err != nil { 1069 return 1070 } 1071 1072 // After a successful indexing, mark the revision as fully indexed. 1073 err = i.tlfDb.Put(ctx, m.tlfID, rev, kbfsmd.RevisionUninitialized) 1074 }() 1075 1076 return i.indexNewlySyncedTlf(ctx, fs, rev) 1077 } 1078 1079 func (i *Indexer) doIncrementalIndex( 1080 ctx context.Context, m tlfMessage, indexedRev, newRev kbfsmd.Revision) ( 1081 err error) { 1082 i.log.CDebugf( 1083 ctx, "Incremental index %s: %d -> %d", m.tlfID, indexedRev, newRev) 1084 defer func() { 1085 i.log.CDebugf(ctx, "Incremental index %s: %d -> %d: %+v", 1086 m.tlfID, indexedRev, newRev, err) 1087 }() 1088 1089 // Gather list of changes after indexedRev, up to and including newRev. 1090 changes, refSize, err := libkbfs.GetChangesBetweenRevisions( 1091 ctx, i.config, m.tlfID, indexedRev, newRev) 1092 if err != nil { 1093 return err 1094 } 1095 1096 err = i.progress.startIndex(m.tlfID, refSize, indexIncremental) 1097 if err != nil { 1098 return err 1099 } 1100 defer func() { 1101 progErr := i.progress.finishIndex(m.tlfID) 1102 if progErr != nil { 1103 i.log.CDebugf(ctx, "Couldn't finish index: %+v", err) 1104 } 1105 }() 1106 1107 // Sort by path length, to make sure we process directories before 1108 // their children. 1109 sort.Slice(changes, func(i, j int) bool { 1110 return len(changes[i].CurrPath.Path) < len(changes[j].CurrPath.Path) 1111 }) 1112 1113 fs, err := i.fsForRev(ctx, m.tlfID, newRev) 1114 if err != nil { 1115 return err 1116 } 1117 1118 // Save newRev as the started revision. 1119 err = i.tlfDb.Put(ctx, m.tlfID, indexedRev, newRev) 1120 if err != nil { 1121 return err 1122 } 1123 1124 defer func() { 1125 if err != nil { 1126 return 1127 } 1128 1129 // After a successful indexing, mark the revision as fully indexed. 1130 err = i.tlfDb.Put(ctx, m.tlfID, newRev, kbfsmd.RevisionUninitialized) 1131 }() 1132 1133 err = i.refreshBatch(ctx) 1134 if err != nil { 1135 return err 1136 } 1137 1138 defer func() { 1139 flushErr := i.flushBatch(ctx) 1140 if flushErr == nil { 1141 return 1142 } 1143 i.log.CDebugf(ctx, "Error flushing batch: %+v", flushErr) 1144 if err == nil { 1145 err = flushErr 1146 } 1147 }() 1148 1149 newChanges := 0 1150 for _, change := range changes { 1151 if change.IsNew { 1152 newChanges++ 1153 } 1154 } 1155 ids, err := i.blocksDb.GetNextDocIDs(newChanges) 1156 if err != nil { 1157 return err 1158 } 1159 currID := 0 1160 1161 var dirDoneFns []func() error 1162 if len(changes) > 0 { 1163 // Update the root pointer first; it doesn't require re-indexing. 1164 oldPtr := changes[0].OldPtr 1165 changes = changes[1:] 1166 doUpdate := true 1167 _, docID, _, err := i.blocksDb.Get(ctx, oldPtr) 1168 switch errors.Cause(err) { 1169 case nil: 1170 case ldberrors.ErrNotFound: 1171 // The update already happened. 1172 doUpdate = false 1173 default: 1174 return err 1175 } 1176 1177 if doUpdate { 1178 dirDoneFn, err := i.recordUpdatedNodePtr( 1179 ctx, fs.RootNode(), newRev, docID, oldPtr) 1180 if err != nil { 1181 return err 1182 } 1183 defer func() { 1184 if err != nil { 1185 return 1186 } 1187 1188 err = dirDoneFn() 1189 }() 1190 } 1191 } 1192 1193 // Iterate through each change and call the appropriate index 1194 // function for it. 1195 for _, change := range changes { 1196 err := i.checkDone(ctx) 1197 if err != nil { 1198 return err 1199 } 1200 1201 plainPath, _ := change.CurrPath.PlaintextSansTlf() 1202 dir, _ := path.Split(plainPath) 1203 dirFS, err := fs.ChrootAsLibFS(path.Clean(dir)) 1204 if err != nil { 1205 return err 1206 } 1207 1208 dirNode := dirFS.RootNode() 1209 md, err := i.config.KBFSOps().GetNodeMetadata(ctx, dirNode) 1210 if err != nil { 1211 return err 1212 } 1213 _, dirDocID, _, err := i.blocksDb.Get(ctx, md.BlockInfo.BlockPointer) 1214 if err != nil { 1215 return err 1216 } 1217 1218 switch change.Type { 1219 case libkbfs.ChangeTypeWrite: 1220 var dirDoneFn func() error 1221 if change.IsNew { 1222 id := ids[currID] 1223 currID++ 1224 dirDoneFn, err = i.indexChild( 1225 ctx, dirNode, dirDocID, change.CurrPath.TailName(), 1226 id, newRev) 1227 } else { 1228 dirDoneFn, err = i.updateChild( 1229 ctx, dirNode, dirDocID, change.CurrPath.TailName(), 1230 change.OldPtr, newRev) 1231 switch errors.Cause(err).(type) { 1232 case OldPtrNotFound: 1233 // Already updated. 1234 err = nil 1235 default: 1236 } 1237 } 1238 if err != nil { 1239 return err 1240 } 1241 if dirDoneFn != nil { 1242 dirDoneFns = append(dirDoneFns, dirDoneFn) 1243 } 1244 case libkbfs.ChangeTypeRename: 1245 err := i.renameChild( 1246 ctx, dirNode, dirDocID, change.CurrPath.TailName(), newRev) 1247 if err != nil { 1248 return err 1249 } 1250 case libkbfs.ChangeTypeDelete: 1251 err := i.deleteFromUnrefs(ctx, m.tlfID, change.UnrefsForDelete) 1252 if err != nil { 1253 return err 1254 } 1255 default: 1256 i.log.CDebugf(ctx, "Ignoring unknown change type %s", change.Type) 1257 continue 1258 } 1259 } 1260 1261 // Finish all the dirs at the end, since we're not processing them 1262 // recursively. 1263 for _, f := range dirDoneFns { 1264 err := f() 1265 if err != nil { 1266 return err 1267 } 1268 } 1269 1270 return nil 1271 } 1272 1273 func (i *Indexer) handleTlfMessage(ctx context.Context, m tlfMessage) error { 1274 defer i.indexWG.Done() 1275 1276 doUnqueue := true 1277 defer func() { 1278 if doUnqueue { 1279 // We didn't end up indexing this TLF after all. 1280 i.progress.tlfUnqueue(m.tlfID) 1281 } 1282 }() 1283 1284 // Figure out which revision to lock to, for this 1285 // indexing scan. 1286 rev := m.rev 1287 if rev == kbfsmd.RevisionUninitialized { 1288 // TODO(HOTPOT-1504) -- remove indexing if the 1289 // mode is no longer synced. 1290 return nil 1291 } 1292 1293 indexedRev, startedRev, err := i.tlfDb.Get(ctx, m.tlfID) 1294 switch errors.Cause(err) { 1295 case nil: 1296 case ldberrors.ErrNotFound: 1297 default: 1298 return err 1299 } 1300 1301 if rev <= indexedRev { 1302 // No need to re-index this. 1303 return nil 1304 } 1305 1306 if startedRev != kbfsmd.RevisionUninitialized && startedRev != rev { 1307 // We've started indexing a particular revision already; we 1308 // need to continue on at that revision, or risk confusing the 1309 // index. But re-add the message for this revision later. 1310 i.log.CDebugf(ctx, "Finishing incomplete index for revision %s for "+ 1311 "TLF %s, before indexing the requested revision %d", 1312 startedRev, m.tlfID, rev) 1313 rev = startedRev 1314 i.indexWG.Add(1) 1315 select { 1316 case i.tlfCh <- m: 1317 default: 1318 i.indexWG.Done() 1319 i.log.CDebugf( 1320 context.Background(), "Couldn't send TLF message for %s/%d", 1321 m.tlfID, m.rev) 1322 } 1323 } 1324 1325 doUnqueue = false 1326 if indexedRev != kbfsmd.RevisionUninitialized { 1327 err = i.doIncrementalIndex(ctx, m, indexedRev, rev) 1328 } else { 1329 err = i.doFullIndex(ctx, m, rev) 1330 } 1331 1332 switch errors.Cause(err).(type) { 1333 case nil: 1334 case RevisionGCdError: 1335 // TODO(HOTPOT-1504) -- remove all documents from the index 1336 // and trigger a new indexing at the latest revision. 1337 i.log.CDebugf( 1338 ctx, "Ignoring a GC-revision failure for now (HOTPOT-1504): %+v", 1339 err) 1340 return nil 1341 default: 1342 return err 1343 } 1344 1345 return nil 1346 } 1347 1348 func (i *Indexer) loop(ctx context.Context) { 1349 defer i.loopWG.Done() 1350 1351 i.log.CDebugf(ctx, "Starting indexing loop") 1352 defer i.log.CDebugf(ctx, "Ending index loop") 1353 1354 // Wait for KBFSOps to be initialized, which might happen later 1355 // after the indexer. 1356 for i.config.KBFSOps() == nil { 1357 time.Sleep(1 * time.Second) 1358 } 1359 i.remoteStatus.Init(ctx, i.log, i.config, i) 1360 for i.config.Notifier() == nil { 1361 time.Sleep(1 * time.Second) 1362 } 1363 err := i.config.Notifier().RegisterForSyncedTlfs(i) 1364 if err != nil { 1365 i.log.CWarningf( 1366 ctx, "Couldn't register for synced TLF updates: %+v", err) 1367 } 1368 1369 outerLoop: 1370 for { 1371 err := i.loadIndex(ctx) 1372 if err != nil { 1373 i.log.CDebugf(ctx, "Couldn't load index: %+v", err) 1374 } 1375 1376 state := keybase1.MobileAppState_FOREGROUND 1377 kbCtx := i.config.KbContext() 1378 for { 1379 select { 1380 case <-i.userChangedCh: 1381 // Re-load the index on each login/logout event. 1382 i.log.CDebugf(ctx, "User changed") 1383 continue outerLoop 1384 case state = <-kbCtx.NextAppStateUpdate(&state): 1385 // TODO(HOTPOT-1494): once we are doing actual 1386 // indexing in a separate goroutine, pause/unpause it 1387 // via a channel send from here. 1388 for state != keybase1.MobileAppState_FOREGROUND { 1389 i.log.CDebugf(ctx, 1390 "Pausing indexing while not foregrounded: state=%s", 1391 state) 1392 state = <-kbCtx.NextAppStateUpdate(&state) 1393 } 1394 i.log.CDebugf(ctx, "Resuming indexing while foregrounded") 1395 continue 1396 case m := <-i.tlfCh: 1397 ctx := i.makeContext(ctx) 1398 i.log.CDebugf( 1399 ctx, "Received TLF message for %s, rev=%d", m.tlfID, m.rev) 1400 1401 err = i.handleTlfMessage(ctx, m) 1402 if err != nil { 1403 i.log.CDebugf(ctx, "Error handling TLF message: %+v", err) 1404 } 1405 case <-ctx.Done(): 1406 return 1407 case <-i.shutdownCh: 1408 i.cancelLoop() 1409 return 1410 } 1411 } 1412 } 1413 } 1414 1415 // Shutdown shuts down this indexer. 1416 func (i *Indexer) Shutdown(ctx context.Context) error { 1417 close(i.shutdownCh) 1418 err := i.loopWG.Wait(ctx) 1419 if err != nil { 1420 return err 1421 } 1422 1423 i.lock.Lock() 1424 defer i.lock.Unlock() 1425 1426 return i.closeIndexLocked(ctx) 1427 } 1428 1429 func (i *Indexer) waitForIndex(ctx context.Context) error { 1430 ch := func() <-chan struct{} { 1431 i.lock.RLock() 1432 defer i.lock.RUnlock() 1433 return i.indexReadyCh 1434 }() 1435 1436 select { 1437 case <-ch: 1438 return nil 1439 case <-ctx.Done(): 1440 return ctx.Err() 1441 } 1442 } 1443 1444 func (i *Indexer) waitForSyncs(ctx context.Context) error { 1445 return i.indexWG.Wait(ctx) 1446 } 1447 1448 // Search executes the given query and returns the results in the form 1449 // of full KBFS paths to each hit. `numResults` limits the number of 1450 // returned results, and `startingResult` indicates the number of 1451 // results that have been previously fetched -- basically it indicates 1452 // the starting index number of the next page of desired results. The 1453 // return parameter `nextResult` indicates what `startingResult` could 1454 // be set to next time, to get more results, where -1 indicates that 1455 // there are no more results. 1456 func (i *Indexer) Search( 1457 ctx context.Context, query string, numResults, startingResult int) ( 1458 results []Result, nextResult int, err error) { 1459 if numResults == 0 { 1460 return nil, 0, nil 1461 } 1462 1463 i.lock.RLock() 1464 defer i.lock.RUnlock() 1465 1466 if i.index == nil { 1467 return nil, 0, errors.New("Index not loaded") 1468 } 1469 1470 sQuery := bleve.NewQueryStringQuery(query) 1471 nextResult = startingResult 1472 results = make([]Result, 0, numResults) 1473 usedPaths := make(map[string]bool) 1474 resultLoop: 1475 for len(results) < numResults { 1476 req := bleve.NewSearchRequestOptions( 1477 sQuery, numResults, nextResult, false) 1478 indexResults, err := i.index.Search(req) 1479 if err != nil { 1480 return nil, 0, err 1481 } 1482 1483 // Build up the path for each result. 1484 for j, hit := range indexResults.Hits { 1485 docID := hit.ID 1486 var p []string // reversed list of path components 1487 for docID != "" { 1488 parentDocID, name, err := i.docDb.Get( 1489 ctx, strings.TrimPrefix(docID, nameDocIDPrefix)) 1490 if err != nil { 1491 return nil, 0, err 1492 } 1493 p = append(p, name) 1494 docID = parentDocID 1495 } 1496 1497 // Reverse the path name. 1498 for k := len(p)/2 - 1; k >= 0; k-- { 1499 opp := len(p) - 1 - k 1500 p[k], p[opp] = p[opp], p[k] 1501 } 1502 fullPath := path.Join(p...) 1503 if usedPaths[fullPath] { 1504 continue 1505 } 1506 usedPaths[fullPath] = true 1507 results = append(results, Result{fullPath}) 1508 1509 if len(results) >= numResults { 1510 nextResult += j + 1 1511 break resultLoop 1512 } 1513 } 1514 1515 nextResult += len(indexResults.Hits) 1516 if len(indexResults.Hits) < numResults { 1517 nextResult = -1 1518 break 1519 } 1520 } 1521 1522 return results, nextResult, nil 1523 } 1524 1525 // ResetIndex shuts down the current indexer, completely removes its 1526 // on-disk presence, and then restarts it as a blank index. 1527 func (i *Indexer) ResetIndex(ctx context.Context) (err error) { 1528 i.log.CDebugf(ctx, "Resetting the index") 1529 defer func() { i.log.CDebugf(ctx, "Done resetting the index: %+v", err) }() 1530 1531 err = i.Shutdown(ctx) 1532 if err != nil { 1533 return err 1534 } 1535 1536 dir := filepath.Join(i.config.StorageRoot(), indexStorageDir) 1537 err = ioutil.RemoveAll(dir) 1538 if err != nil { 1539 return err 1540 } 1541 1542 i.startLoop() 1543 return nil 1544 } 1545 1546 // Progress returns the progress instance of this indexer. 1547 func (i *Indexer) Progress() *Progress { 1548 return i.progress 1549 }