gitlab.com/SkynetLabs/skyd@v1.6.9/cmd/skyc/helpers.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/vbauerster/mpb/v5" 15 "github.com/vbauerster/mpb/v5/decor" 16 "gitlab.com/SkynetLabs/skyd/skykey" 17 "gitlab.com/SkynetLabs/skyd/skymodules" 18 "go.sia.tech/siad/node/api" 19 "go.sia.tech/siad/types" 20 ) 21 22 // abs returns the absolute representation of a path. 23 // TODO: bad things can happen if you run siac from a non-existent directory. 24 // Implement some checks to catch this problem. 25 func abs(path string) string { 26 abspath, err := filepath.Abs(path) 27 if err != nil { 28 return path 29 } 30 return abspath 31 } 32 33 // absDuration is a small helper function that sanitizes the output for the 34 // given time duration. If the duration is less than 0 it will return 0, 35 // otherwise it will return the duration rounded to the nearest second. 36 func absDuration(t time.Duration) time.Duration { 37 if t <= 0 { 38 return 0 39 } 40 return t.Round(time.Second) 41 } 42 43 // askForConfirmation prints a question and waits for confirmation until the 44 // user gives a valid answer ("y", "yes", "n", "no" with any capitalization). 45 func askForConfirmation(s string) bool { 46 r := bufio.NewReader(os.Stdin) 47 for { 48 fmt.Printf("%s [y/n]: ", s) 49 answer, err := r.ReadString('\n') 50 if err != nil { 51 log.Fatal(err) 52 } 53 answer = strings.ToLower(strings.TrimSpace(answer)) 54 if answer == "y" || answer == "yes" { 55 return true 56 } else if answer == "n" || answer == "no" { 57 return false 58 } 59 } 60 } 61 62 // calculateAverageUint64 calculates the average of a uint64 slice and returns the average as a uint64 63 func calculateAverageUint64(input []uint64) uint64 { 64 total := uint64(0) 65 if len(input) == 0 { 66 return 0 67 } 68 for _, v := range input { 69 total += v 70 } 71 return total / uint64(len(input)) 72 } 73 74 // calculateMedianUint64 calculates the median of a uint64 slice and returns the median as a uint64 75 func calculateMedianUint64(mm []uint64) uint64 { 76 sort.Slice(mm, func(i, j int) bool { return mm[i] < mm[j] }) // sort the numbers 77 78 mNumber := len(mm) / 2 79 80 if len(mm)%2 == 0 { 81 return mm[mNumber] 82 } 83 84 return (mm[mNumber-1] + mm[mNumber]) / 2 85 } 86 87 func fileExists(filename string) bool { 88 info, err := os.Stat(filename) 89 if os.IsNotExist(err) { 90 return false 91 } 92 return !info.IsDir() 93 } 94 95 // estimatedHeightAt returns the estimated block height for the given time. 96 // Block height is estimated by calculating the minutes since a known block in 97 // the past and dividing by 10 minutes (the block time). 98 func estimatedHeightAt(t time.Time, cg api.ConsensusGET) types.BlockHeight { 99 gt := cg.GenesisTimestamp 100 bf := cg.BlockFrequency 101 return types.BlockHeight(types.Timestamp(t.Unix())-gt) / bf 102 } 103 104 // newProgressReader is a helper method for adding a new progress bar to an 105 // existing *mpb.Progress object. 106 func newProgressReader(pbs *mpb.Progress, size int64, filename string, file io.Reader) (*mpb.Bar, io.ReadCloser) { 107 bar := pbs.AddBar( 108 size, 109 mpb.PrependDecorators( 110 decor.Name(pBarJobUpload, decor.WC{W: 10}), 111 decor.Percentage(decor.WC{W: 6}), 112 ), 113 mpb.AppendDecorators( 114 decor.Name(filename, decor.WC{W: len(filename) + 1, C: decor.DidentRight}), 115 ), 116 ) 117 return bar, bar.ProxyReader(file) 118 } 119 120 // newProgressSpinner creates a spinner that is queued after `afterBar` is 121 // complete. 122 func newProgressSpinner(pbs *mpb.Progress, afterBar *mpb.Bar, filename string) *mpb.Bar { 123 return pbs.AddSpinner( 124 1, 125 mpb.SpinnerOnMiddle, 126 mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), 127 mpb.BarQueueAfter(afterBar), 128 mpb.BarFillerClearOnComplete(), 129 mpb.PrependDecorators( 130 decor.OnComplete(decor.Name(pBarJobProcess, decor.WC{W: 10}), pBarJobDone), 131 decor.Name("", decor.WC{W: 6, C: decor.DidentRight}), 132 ), 133 mpb.AppendDecorators( 134 decor.Name(filename, decor.WC{W: len(filename) + 1, C: decor.DidentRight}), 135 ), 136 ) 137 } 138 139 // parseAndAddSkykey is a helper that parses any supplied skykey and adds it to 140 // the SkyfileUploadParameters 141 func parseAndAddSkykey(sup skymodules.SkyfileUploadParameters) skymodules.SkyfileUploadParameters { 142 if skykeyName != "" && skykeyID != "" { 143 die("Can only use either skykeyname or skykeyid flag, not both.") 144 } 145 // Set Encrypt param to true if a skykey ID or name is set. 146 if skykeyName != "" { 147 sup.SkykeyName = skykeyName 148 } else if skykeyID != "" { 149 var ID skykey.SkykeyID 150 err := ID.FromString(skykeyID) 151 if err != nil { 152 die("Unable to parse skykey ID") 153 } 154 sup.SkykeyID = ID 155 } 156 return sup 157 } 158 159 // sanitizeErr is a small helper function that sanitizes the output for the 160 // given error string. It will print "-", if the error string is the equivalent 161 // of a nil error. 162 func sanitizeErr(errStr string) string { 163 if errStr == "" { 164 return "-" 165 } 166 if !verbose && len(errStr) > truncateErrLength { 167 errStr = errStr[:truncateErrLength] + "..." 168 } 169 return errStr 170 } 171 172 // sanitizeTime is a small helper function that sanitizes the output for the 173 // given time. If the given 'cond' value is false, it will print "-", if it is 174 // true it will print the time in a predefined format. 175 func sanitizeTime(t time.Time, cond bool) string { 176 if !cond { 177 return "-" 178 } 179 return fmt.Sprintf("%v", t.Format(time.RFC3339)) 180 } 181 182 // validateSkyKeyNameAndIDUsage validates the usage of name and ID, ensuring 183 // that only one is used. 184 func validateSkyKeyNameAndIDUsage(name, id string) error { 185 if name == "" && id == "" { 186 return errNeitherNameNorIDUsed 187 } 188 if name != "" && id != "" { 189 return errBothNameAndIDUsed 190 } 191 return nil 192 }