github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/chunks/handle_truncate.go (about) 1 package chunksPkg 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 11 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" 13 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index" 14 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 15 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/manifest" 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/monitor" 17 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output" 18 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 19 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/usage" 20 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk" 21 ) 22 23 // TODO: We need to make sure, when we truncate, that we truncate the corresponding maps files as well. 24 25 func (opts *ChunksOptions) HandleTruncate(rCtx *output.RenderCtx, blockNums []base.Blknum) error { 26 chain := opts.Globals.Chain 27 if opts.Globals.TestMode { 28 logger.Warn("Truncate option not tested.") 29 return nil 30 } 31 32 if !opts.Globals.IsApiMode() && 33 !usage.QueryUser(strings.Replace(truncateWarning, "{0}", fmt.Sprintf("%d", opts.Truncate), -1), "Not truncated") { 34 return nil 35 } 36 37 _ = file.CleanFolder(chain, config.PathToIndex(chain), []string{"ripe", "unripe", "maps", "staging"}) 38 39 showProgress := opts.Globals.ShowProgressNotTesting() 40 bar := logger.NewBar(logger.BarOptions{ 41 Enabled: showProgress, 42 Total: 128, 43 Type: logger.Expanding, 44 }) 45 46 fetchData := func(modelChan chan types.Modeler, errorChan chan error) { 47 48 // First, we will remove the chunks and update the manifest. We do this separately for 49 // each chunk, so that if we get interrupted, we have a relatively sane state (although, 50 // we will have to manually repair the index with chifra init --all if this fails. Keep track 51 // of the last chunks remaining. 52 latestChunk := base.Blknum(0) 53 nChunksRemoved := 0 54 truncateIndex := func(walker *walk.CacheWalker, path string, first bool) (bool, error) { 55 if path != index.ToBloomPath(path) { 56 logger.Fatal("should not happen ==> we're spinning through the bloom filters") 57 } 58 59 if strings.HasSuffix(path, ".gz") { 60 os.Remove(path) 61 return true, nil 62 } 63 64 rng, err := base.RangeFromFilenameE(path) 65 if err != nil { 66 return false, err 67 } 68 69 testRange := base.FileRange{First: opts.Truncate, Last: base.NOPOSN} 70 if rng.Intersects(testRange) { 71 if err = manifest.RemoveChunk(chain, opts.PublisherAddr, index.ToBloomPath(path), index.ToIndexPath(path)); err != nil { 72 return false, err 73 } 74 bar.Prefix = fmt.Sprintf("Removing %s ", rng) 75 nChunksRemoved++ 76 } else { 77 // We did not remove the chunk, so we need to keep track of where the truncated index ends 78 latestChunk = base.Max(latestChunk, rng.Last) 79 bar.Prefix = fmt.Sprintf("Not removing %s", rng) 80 } 81 bar.Tick() 82 return true, nil 83 } 84 85 walker := walk.NewCacheWalker( 86 chain, 87 opts.Globals.TestMode, 88 100, /* maxTests */ 89 truncateIndex, 90 ) 91 if err := walker.WalkBloomFilters(blockNums); err != nil { 92 errorChan <- err 93 rCtx.Cancel() 94 95 } else { 96 bar.Prefix = fmt.Sprintf("Truncated to %d ", opts.Truncate) 97 bar.Finish(true /* newLine */) 98 bar = logger.NewBar(logger.BarOptions{ 99 Enabled: showProgress, 100 Total: 20, 101 Type: logger.Expanding, 102 }) 103 104 // We've made it this far (removed chunks and updated manifest) now we need to remove appearances 105 // from any monitors that may exist which happen after the truncated block. Also, update the monitors' 106 // header to reflect this new lastScanned block. 107 nMonitorsTruncated := 0 108 truncateMonitor := func(path string, info fs.FileInfo, err error) error { 109 if err != nil { 110 return err 111 } 112 if !info.IsDir() && strings.HasSuffix(path, ".mon.bin") { 113 addr, _ := base.AddressFromPath(path, ".mon.bin") 114 bar.Prefix = fmt.Sprintf("Truncating monitor for %s", addr.Hex()) 115 if !addr.IsZero() { 116 mon, _ := monitor.NewMonitor(chain, addr, false /* create */) 117 var removed bool 118 if removed, err = mon.TruncateTo(chain, uint32(latestChunk)); err != nil { 119 return err 120 } 121 if removed { 122 nMonitorsTruncated++ 123 } 124 } 125 bar.Tick() 126 } 127 return nil 128 } 129 _ = filepath.Walk(filepath.Join(config.PathToCache(chain), "monitors"), truncateMonitor) 130 bar.Prefix = fmt.Sprintf("Truncated monitors to %d ", opts.Truncate) 131 bar.Finish(true /* newLine */) 132 133 // All that's left to do is report on what happened. 134 fin := "." 135 if nChunksRemoved > 0 { 136 fin = ", the manifest was updated." 137 } 138 msg1 := fmt.Sprintf("Truncated index to block %d (the latest full chunk).", latestChunk) 139 msg2 := fmt.Sprintf("%d chunks removed, %d monitors truncated%s", nChunksRemoved, nMonitorsTruncated, fin) 140 if opts.Globals.Format == "json" { 141 s := types.Message{ 142 Msg: msg1 + " " + msg2, 143 } 144 modelChan <- &s 145 } else { 146 logger.Info(msg1) 147 logger.Info(msg2) 148 } 149 } 150 } 151 152 opts.Globals.NoHeader = true 153 return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOpts()) 154 } 155 156 var truncateWarning = `Are sure you want to remove index chunks after and including block {0} (Yn)? `