github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/scrape/options.go (about)

     1  // Copyright 2016, 2024 The TrueBlocks Authors. All rights reserved.
     2  // Use of this source code is governed by a license that can
     3  // be found in the LICENSE file.
     4  /*
     5   * Parts of this file were auto generated. Edit only those parts of
     6   * the code inside of 'EXISTING_CODE' tags.
     7   */
     8  
     9  package scrapePkg
    10  
    11  import (
    12  	// EXISTING_CODE
    13  	"encoding/json"
    14  	"io"
    15  	"net/http"
    16  	"net/url"
    17  	"os"
    18  	"os/user"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  
    23  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/internal/globals"
    24  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    25  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/caps"
    26  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    27  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/configtypes"
    28  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    29  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    30  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    31  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    32  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    33  	// EXISTING_CODE
    34  )
    35  
    36  // ScrapeOptions provides all command options for the chifra scrape command.
    37  type ScrapeOptions struct {
    38  	BlockCnt  uint64                     `json:"blockCnt,omitempty"`  // Maximum number of blocks to process per pass
    39  	Sleep     float64                    `json:"sleep,omitempty"`     // Seconds to sleep between scraper passes
    40  	Publisher string                     `json:"publisher,omitempty"` // For some query options, the publisher of the index
    41  	Touch     base.Blknum                `json:"touch,omitempty"`     // First block to visit when scraping (snapped back to most recent snap_to_grid mark)
    42  	RunCount  uint64                     `json:"runCount,omitempty"`  // Run the scraper this many times, then quit
    43  	DryRun    bool                       `json:"dryRun,omitempty"`    // Show the configuration that would be applied if run,no changes are made
    44  	Notify    bool                       `json:"notify,omitempty"`    // Enable the notify feature
    45  	Settings  configtypes.ScrapeSettings `json:"settings,omitempty"`  // Configuration items for the scrape
    46  	Globals   globals.GlobalOptions      `json:"globals,omitempty"`   // The global options
    47  	Conn      *rpc.Connection            `json:"conn,omitempty"`      // The connection to the RPC server
    48  	BadFlag   error                      `json:"badFlag,omitempty"`   // An error flag if needed
    49  	// EXISTING_CODE
    50  	PublisherAddr base.Address `json:"-"`
    51  	// EXISTING_CODE
    52  }
    53  
    54  var defaultScrapeOptions = ScrapeOptions{
    55  	BlockCnt: 2000,
    56  	Sleep:    14,
    57  }
    58  
    59  // testLog is used only during testing to export the options for this test case.
    60  func (opts *ScrapeOptions) testLog() {
    61  	logger.TestLog(opts.BlockCnt != 2000, "BlockCnt: ", opts.BlockCnt)
    62  	logger.TestLog(opts.Sleep != float64(14), "Sleep: ", opts.Sleep)
    63  	logger.TestLog(len(opts.Publisher) > 0, "Publisher: ", opts.Publisher)
    64  	logger.TestLog(opts.Touch != 0, "Touch: ", opts.Touch)
    65  	logger.TestLog(opts.RunCount != 0, "RunCount: ", opts.RunCount)
    66  	logger.TestLog(opts.DryRun, "DryRun: ", opts.DryRun)
    67  	logger.TestLog(opts.Notify, "Notify: ", opts.Notify)
    68  	opts.Settings.TestLog(opts.Globals.Chain, opts.Globals.TestMode)
    69  	opts.Conn.TestLog(opts.getCaches())
    70  	opts.Globals.TestLog()
    71  }
    72  
    73  // String implements the Stringer interface
    74  func (opts *ScrapeOptions) String() string {
    75  	b, _ := json.MarshalIndent(opts, "", "  ")
    76  	return string(b)
    77  }
    78  
    79  // scrapeFinishParseApi finishes the parsing for server invocations. Returns a new ScrapeOptions.
    80  func scrapeFinishParseApi(w http.ResponseWriter, r *http.Request) *ScrapeOptions {
    81  	values := r.URL.Query()
    82  	if r.Header.Get("User-Agent") == "testRunner" {
    83  		values.Set("testRunner", "true")
    84  	}
    85  	return ScrapeFinishParseInternal(w, values)
    86  }
    87  
    88  func ScrapeFinishParseInternal(w io.Writer, values url.Values) *ScrapeOptions {
    89  	copy := defaultScrapeOptions
    90  	copy.Globals.Caps = getCaps()
    91  	opts := &copy
    92  	opts.BlockCnt = 2000
    93  	opts.Sleep = 14
    94  	opts.Settings.AppsPerChunk = 2000000
    95  	opts.Settings.SnapToGrid = 250000
    96  	opts.Settings.FirstSnap = 2000000
    97  	opts.Settings.UnripeDist = 28
    98  	opts.Settings.ChannelCount = 20
    99  	configs := make(map[string]string, 10)
   100  	for key, value := range values {
   101  		switch key {
   102  		case "blockCnt":
   103  			opts.BlockCnt = base.MustParseUint64(value[0])
   104  		case "sleep":
   105  			opts.Sleep = base.MustParseFloat64(value[0])
   106  		case "publisher":
   107  			opts.Publisher = value[0]
   108  		case "touch":
   109  			opts.Touch = base.MustParseBlknum(value[0])
   110  		case "runCount":
   111  			opts.RunCount = base.MustParseUint64(value[0])
   112  		case "dryRun":
   113  			opts.DryRun = true
   114  		case "notify":
   115  			opts.Notify = true
   116  		case "appsPerChunk":
   117  			configs[key] = value[0]
   118  		case "snapToGrid":
   119  			configs[key] = value[0]
   120  		case "firstSnap":
   121  			configs[key] = value[0]
   122  		case "unripeDist":
   123  			configs[key] = value[0]
   124  		case "channelCount":
   125  			configs[key] = value[0]
   126  		case "allowMissing":
   127  			configs[key] = value[0]
   128  		default:
   129  			if !copy.Globals.Caps.HasKey(key) {
   130  				err := validate.Usage("Invalid key ({0}) in {1} route.", key, "scrape")
   131  				if opts.BadFlag == nil || opts.BadFlag.Error() > err.Error() {
   132  					opts.BadFlag = err
   133  				}
   134  			}
   135  		}
   136  	}
   137  	opts.Conn = opts.Globals.FinishParseApi(w, values, opts.getCaches())
   138  	opts.Publisher, _ = opts.Conn.GetEnsAddress(config.GetPublisher(opts.Publisher))
   139  	opts.PublisherAddr = base.HexToAddress(opts.Publisher)
   140  
   141  	// EXISTING_CODE
   142  	config.SetScrapeArgs(opts.Globals.Chain, configs)
   143  	// EXISTING_CODE
   144  
   145  	return opts
   146  }
   147  
   148  // scrapeFinishParse finishes the parsing for command line invocations. Returns a new ScrapeOptions.
   149  func scrapeFinishParse(args []string) *ScrapeOptions {
   150  	// remove duplicates from args if any (not needed in api mode because the server does it).
   151  	dedup := map[string]int{}
   152  	if len(args) > 0 {
   153  		tmp := []string{}
   154  		for _, arg := range args {
   155  			if value := dedup[arg]; value == 0 {
   156  				tmp = append(tmp, arg)
   157  			}
   158  			dedup[arg]++
   159  		}
   160  		args = tmp
   161  	}
   162  
   163  	defFmt := "txt"
   164  	opts := GetOptions()
   165  	opts.Conn = opts.Globals.FinishParse(args, opts.getCaches())
   166  	opts.Publisher, _ = opts.Conn.GetEnsAddress(config.GetPublisher(opts.Publisher))
   167  	opts.PublisherAddr = base.HexToAddress(opts.Publisher)
   168  
   169  	// EXISTING_CODE
   170  	configs := getConfigCmdsFromArgs()
   171  	config.SetScrapeArgs(opts.Globals.Chain, configs)
   172  	if len(args) == 1 && (args[0] == "run" || args[0] == "indexer") {
   173  		// these options have been deprecated, so do nothing
   174  	} else if len(args) > 1 {
   175  		opts.BadFlag = validate.Usage("Invalid argument {0}", args[0])
   176  	}
   177  	// EXISTING_CODE
   178  	if len(opts.Globals.Format) == 0 || opts.Globals.Format == "none" {
   179  		opts.Globals.Format = defFmt
   180  	}
   181  
   182  	return opts
   183  }
   184  
   185  func GetOptions() *ScrapeOptions {
   186  	// EXISTING_CODE
   187  	// EXISTING_CODE
   188  	return &defaultScrapeOptions
   189  }
   190  
   191  func getCaps() caps.Capability {
   192  	var capabilities caps.Capability // capabilities for chifra scrape
   193  	capabilities = capabilities.Add(caps.Verbose)
   194  	capabilities = capabilities.Add(caps.Version)
   195  	capabilities = capabilities.Add(caps.Noop)
   196  	capabilities = capabilities.Add(caps.NoColor)
   197  	capabilities = capabilities.Add(caps.Chain)
   198  	// EXISTING_CODE
   199  	// EXISTING_CODE
   200  	return capabilities
   201  }
   202  
   203  func ResetOptions(testMode bool) {
   204  	// We want to keep writer between command file calls
   205  	w := GetOptions().Globals.Writer
   206  	opts := ScrapeOptions{}
   207  	globals.SetDefaults(&opts.Globals)
   208  	opts.Globals.TestMode = testMode
   209  	opts.Globals.Writer = w
   210  	opts.Globals.Caps = getCaps()
   211  	opts.BlockCnt = 2000
   212  	opts.Sleep = 14
   213  	opts.Settings.AppsPerChunk = 2000000
   214  	opts.Settings.SnapToGrid = 250000
   215  	opts.Settings.FirstSnap = 2000000
   216  	opts.Settings.UnripeDist = 28
   217  	opts.Settings.ChannelCount = 20
   218  	defaultScrapeOptions = opts
   219  }
   220  
   221  func (opts *ScrapeOptions) getCaches() (caches map[walk.CacheType]bool) {
   222  	// EXISTING_CODE
   223  	// EXISTING_CODE
   224  	return
   225  }
   226  
   227  // EXISTING_CODE
   228  // getPidFilePath finds the best path for a pid file. It first tries to use
   229  // "variable directory" (e.g. /run/{user}/ on Linux), if that fails it falls back
   230  // to os.TempDir()
   231  func (opts *ScrapeOptions) getPidFilePath() string {
   232  	var pidfileDir string
   233  	if runtime.GOOS == "darwin" {
   234  		// MacOS
   235  		pidfileDir = filepath.Join("/usr/local/var", "run")
   236  	} else {
   237  		// Linux
   238  		// On Linux only root can write to the main directory /run, but every logged-in
   239  		// user has its own writable subdirectory with the same name as user's UID
   240  		user, err := user.Current()
   241  		if err == nil {
   242  			pidfileDir = filepath.Join("/run/user/", user.Uid)
   243  		}
   244  	}
   245  	// Fallback to temp dir
   246  	if pidfileDir == "" || !file.FolderExists(pidfileDir) {
   247  		pidfileDir = os.TempDir()
   248  	}
   249  	return filepath.Join(pidfileDir, "chifra", "scrape", strings.ToLower(opts.Globals.Chain)+".pid")
   250  }
   251  
   252  func getConfigCmdsFromArgs() map[string]string {
   253  	configs := make(map[string]string, 10)
   254  	for i := 0; i < len(os.Args); i++ {
   255  		arg := os.Args[i]
   256  		next := ""
   257  		if i < len(os.Args)-1 {
   258  			next = os.Args[i+1]
   259  		}
   260  		switch arg {
   261  		case "--apps_per_chunk":
   262  			configs["appsPerChunk"] = next
   263  		case "--snap_to_grid":
   264  			configs["snapToGrid"] = next
   265  		case "--first_snap":
   266  			configs["firstSnap"] = next
   267  		case "--unripe_dist":
   268  			configs["unripeDist"] = next
   269  		case "--channel_count":
   270  			configs["channelCount"] = next
   271  		case "--allow_missing":
   272  			configs["allowMissing"] = "true"
   273  		}
   274  	}
   275  	return configs
   276  }
   277  
   278  // EXISTING_CODE