1// Copyright 2024 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"fmt"
19	"io"
20	"net/http"
21	"strings"
22)
23
24func step(desc string, f func(*stepPrinter) error) error {
25	fmt.Printf("%s...", desc)
26	if *pipe {
27		fmt.Printf("\n")
28	} else {
29		fmt.Printf(" ")
30	}
31	s := stepPrinter{lastPercent: -1}
32	err := f(&s)
33	s.erasePercent()
34	if err != nil {
35		fmt.Printf("ERROR\n")
36	} else {
37		fmt.Printf("OK\n")
38	}
39	return err
40}
41
42type stepPrinter struct {
43	lastPercent     int
44	percentLen      int
45	progress, total int
46}
47
48func (s *stepPrinter) erasePercent() {
49	if !*pipe && s.percentLen > 0 {
50		var erase strings.Builder
51		for i := 0; i < s.percentLen; i++ {
52			erase.WriteString("\b \b")
53		}
54		fmt.Printf("%s", erase.String())
55		s.percentLen = 0
56	}
57}
58
59func (s *stepPrinter) setTotal(total int) {
60	s.progress = 0
61	s.total = total
62	s.printPercent()
63}
64
65func (s *stepPrinter) addProgress(delta int) {
66	s.progress += delta
67	s.printPercent()
68}
69
70func (s *stepPrinter) printPercent() {
71	if s.total <= 0 {
72		return
73	}
74
75	percent := 100
76	if s.progress < s.total {
77		percent = 100 * s.progress / s.total
78	}
79	if *pipe {
80		percent -= percent % 10
81	}
82	if percent == s.lastPercent {
83		return
84	}
85
86	s.erasePercent()
87
88	s.lastPercent = percent
89	str := fmt.Sprintf("%d%%", percent)
90	s.percentLen = len(str)
91	fmt.Printf("%s", str)
92	if *pipe {
93		fmt.Printf("\n")
94	}
95}
96
97func (s *stepPrinter) progressWriter(total int) io.Writer {
98	s.setTotal(total)
99	return &progressWriter{step: s}
100}
101
102func (s *stepPrinter) httpBodyWithProgress(r *http.Response) io.Reader {
103	// This does not always give any progress. It seems GitHub will sometimes
104	// provide a Content-Length header and sometimes not, for the same URL.
105	return io.TeeReader(r.Body, s.progressWriter(int(r.ContentLength)))
106}
107
108type progressWriter struct {
109	step *stepPrinter
110}
111
112func (p *progressWriter) Write(b []byte) (int, error) {
113	p.step.addProgress(len(b))
114	return len(b), nil
115}
116