dl: format download progress in human readable form

Fixes golang/go#45556

Change-Id: Idabdceb1063f3544b805f45dd2e034cc459fda91
Reviewed-on: https://go-review.googlesource.com/c/dl/+/339869
Reviewed-by: Alex Rakoczy <alex@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alex Rakoczy <alex@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/internal/version/version.go b/internal/version/version.go
index 275d6cc..709eca4 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -23,6 +23,7 @@
 	"path"
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -75,6 +76,28 @@
 	os.Exit(0)
 }
 
+func fmtSize(size int64) string {
+	const (
+		byte_unit = 1 << (10 * iota)
+		kilobyte_unit
+		megabyte_unit
+	)
+
+	unit := "B"
+	value := float64(size)
+
+	switch {
+	case size >= megabyte_unit:
+		unit = "MB"
+		value = value / megabyte_unit
+	case size >= kilobyte_unit:
+		unit = "KB"
+		value = value / kilobyte_unit
+	}
+	formatted := strings.TrimSuffix(strconv.FormatFloat(value, 'f', 1, 64), ".0")
+	return fmt.Sprintf("%s %s", formatted, unit)
+}
+
 // install installs a version of Go to the named target directory, creating the
 // directory as needed.
 func install(targetDir, version string) error {
@@ -333,7 +356,7 @@
 	if res.StatusCode != http.StatusOK {
 		return errors.New(res.Status)
 	}
-	pw := &progressWriter{w: f, total: res.ContentLength}
+	pw := &progressWriter{w: f, total: res.ContentLength, output: os.Stderr}
 	n, err := io.Copy(pw, res.Body)
 	if err != nil {
 		return err
@@ -346,10 +369,12 @@
 }
 
 type progressWriter struct {
-	w     io.Writer
-	n     int64
-	total int64
-	last  time.Time
+	w         io.Writer
+	n         int64
+	total     int64
+	last      time.Time
+	formatted bool
+	output    io.Writer
 }
 
 func (p *progressWriter) update() {
@@ -357,9 +382,15 @@
 	if p.n == p.total {
 		end = ""
 	}
-	fmt.Fprintf(os.Stderr, "Downloaded %5.1f%% (%*d / %d bytes)%s\n",
-		(100.0*float64(p.n))/float64(p.total),
-		ndigits(p.total), p.n, p.total, end)
+	if p.formatted {
+		fmt.Fprintf(p.output, "Downloaded %5.1f%% (%s / %s)%s\n",
+			(100.0*float64(p.n))/float64(p.total),
+			fmtSize(p.n), fmtSize(p.total), end)
+	} else {
+		fmt.Fprintf(p.output, "Downloaded %5.1f%% (%*d / %d bytes)%s\n",
+			(100.0*float64(p.n))/float64(p.total),
+			ndigits(p.total), p.n, p.total, end)
+	}
 }
 
 func ndigits(i int64) int {
diff --git a/internal/version/version_test.go b/internal/version/version_test.go
index c08e211..3f3da62 100644
--- a/internal/version/version_test.go
+++ b/internal/version/version_test.go
@@ -5,7 +5,10 @@
 package version
 
 import (
+	"bytes"
+	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -33,3 +36,32 @@
 		}
 	}
 }
+
+func TestFormatted(t *testing.T) {
+	var total int64 = 1
+	var buff = new(bytes.Buffer)
+	var units = []string{"B", "KB", "MB"}
+	for i := 1; i < 4; i++ {
+		pw := &progressWriter{w: nil, total: total, formatted: true, output: buff}
+		pw.update()
+		total *= 1024
+		expected := fmt.Sprintf("%d %s", 1, units[i-1])
+		if !strings.Contains(buff.String(), expected) {
+			t.Errorf("expected: %s recieved: %s", expected, buff.String())
+		}
+	}
+}
+
+func TestUnFormatted(t *testing.T) {
+	var total int64 = 1
+	var buff = new(bytes.Buffer)
+	for i := 1; i < 4; i++ {
+		pw := &progressWriter{w: nil, total: total, formatted: false, output: buff}
+		pw.update()
+		expected := fmt.Sprintf("%d bytes", total)
+		if !strings.Contains(buff.String(), expected) {
+			t.Errorf("expected: %s recieved: %s", expected, buff.String())
+		}
+		total *= 1024
+	}
+}