github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/utils/bytes.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  // forked from https://www.socketloop.com/tutorials/golang-byte-format-example
     4  
     5  // Package utils provides generic utility functions.
     6  package utils
     7  
     8  import (
     9  	"fmt"
    10  	"math"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/Racer159/jackal/src/pkg/message"
    15  	"github.com/defenseunicorns/pkg/helpers"
    16  )
    17  
    18  // RoundUp rounds a float64 to the given number of decimal places.
    19  func RoundUp(input float64, places int) (newVal float64) {
    20  	var round float64
    21  	pow := math.Pow(10, float64(places))
    22  	digit := pow * input
    23  	round = math.Ceil(digit)
    24  	newVal = round / pow
    25  	return
    26  }
    27  
    28  // ByteFormat formats a number of bytes into a human readable string.
    29  func ByteFormat(inputNum float64, precision int) string {
    30  	if precision <= 0 {
    31  		precision = 1
    32  	}
    33  
    34  	var unit string
    35  	var returnVal float64
    36  
    37  	// https://www.techtarget.com/searchstorage/definition/mebibyte-MiB
    38  	if inputNum >= 1000000000 {
    39  		returnVal = RoundUp(inputNum/1000000000, precision)
    40  		unit = " GB" // gigabyte
    41  	} else if inputNum >= 1000000 {
    42  		returnVal = RoundUp(inputNum/1000000, precision)
    43  		unit = " MB" // megabyte
    44  	} else if inputNum >= 1000 {
    45  		returnVal = RoundUp(inputNum/1000, precision)
    46  		unit = " KB" // kilobyte
    47  	} else {
    48  		returnVal = inputNum
    49  		unit = " Byte" // byte
    50  	}
    51  
    52  	if returnVal > 1 {
    53  		unit += "s"
    54  	}
    55  
    56  	return strconv.FormatFloat(returnVal, 'f', precision, 64) + unit
    57  }
    58  
    59  // RenderProgressBarForLocalDirWrite creates a progress bar that continuously tracks the progress of writing files to a local directory and all of its subdirectories.
    60  // NOTE: This function runs infinitely until either completeChan or errChan is triggered, this function should be run in a goroutine while a different thread/process is writing to the directory.
    61  func RenderProgressBarForLocalDirWrite(filepath string, expectedTotal int64, completeChan chan error, updateText string, successText string) {
    62  
    63  	// Create a progress bar
    64  	title := fmt.Sprintf("%s (%s of %s)", updateText, ByteFormat(float64(0), 2), ByteFormat(float64(expectedTotal), 2))
    65  	progressBar := message.NewProgressBar(expectedTotal, title)
    66  
    67  	for {
    68  		select {
    69  		case err := <-completeChan:
    70  			if err == nil {
    71  				// Send success message
    72  				progressBar.Successf("%s (%s)", successText, ByteFormat(float64(expectedTotal), 2))
    73  				completeChan <- nil
    74  				return
    75  			} else {
    76  				progressBar.Stop()
    77  				completeChan <- nil
    78  				return
    79  			}
    80  		default:
    81  			// Read the directory size
    82  			currentBytes, dirErr := helpers.GetDirSize(filepath)
    83  			if dirErr != nil {
    84  				message.Debugf("unable to get updated progress: %s", dirErr.Error())
    85  				time.Sleep(200 * time.Millisecond)
    86  				continue
    87  			}
    88  
    89  			// Update the progress bar with the current size
    90  			title := fmt.Sprintf("%s (%s of %s)", updateText, ByteFormat(float64(currentBytes), 2), ByteFormat(float64(expectedTotal), 2))
    91  			progressBar.Update(currentBytes, title)
    92  			time.Sleep(200 * time.Millisecond)
    93  		}
    94  	}
    95  }