Skip to content

Commit 2236360

Browse files
authored
Merge pull request #213 from cmsax/main
feat: allow user to safely run and manage multiple progressbar http servers
2 parents 9046653 + ae4cef0 commit 2236360

File tree

2 files changed

+63
-29
lines changed

2 files changed

+63
-29
lines changed

progressbar.go

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"errors"
77
"fmt"
88
"io"
9-
"log"
109
"math"
1110
"net/http"
1211
"os"
@@ -449,17 +448,15 @@ func NewOptions64(max int64, options ...Option) *ProgressBar {
449448
go func() {
450449
ticker := time.NewTicker(b.config.spinnerChangeInterval)
451450
defer ticker.Stop()
452-
for {
453-
select {
454-
case <-ticker.C:
455-
if b.IsFinished() {
456-
return
457-
}
458-
if b.IsStarted() {
459-
b.lock.Lock()
460-
b.render()
461-
b.lock.Unlock()
462-
}
451+
452+
for range ticker.C {
453+
if b.IsFinished() {
454+
return
455+
}
456+
if b.IsStarted() {
457+
b.lock.Lock()
458+
b.render()
459+
b.lock.Unlock()
463460
}
464461
}
465462
}()
@@ -1014,27 +1011,48 @@ func (p *ProgressBar) State() State {
10141011

10151012
// StartHTTPServer starts an HTTP server dedicated to serving progress bar updates. This allows you to
10161013
// display the status in various UI elements, such as an OS status bar with an `xbar` extension.
1017-
// It is recommended to run this function in a separate goroutine to avoid blocking the main thread.
1014+
// When the progress bar is finished, call `server.Shutdown()` or `server.Close()` to shut it down manually.
10181015
//
10191016
// hostPort specifies the address and port to bind the server to, for example, "0.0.0.0:19999".
1020-
func (p *ProgressBar) StartHTTPServer(hostPort string) {
1021-
// for advanced users, we can return the data as json
1022-
http.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) {
1023-
w.Header().Set("Content-Type", "text/json")
1024-
// since the state is a simple struct, we can just ignore the error
1017+
func (p *ProgressBar) StartHTTPServer(hostPort string) *http.Server {
1018+
mux := http.NewServeMux()
1019+
1020+
// register routes
1021+
mux.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) {
1022+
w.Header().Set("Content-Type", "application/json")
10251023
bs, _ := json.Marshal(p.State())
10261024
w.Write(bs)
10271025
})
1028-
// for others, we just return the description in a plain text format
1029-
http.HandleFunc("/desc", func(w http.ResponseWriter, r *http.Request) {
1026+
1027+
mux.HandleFunc("/desc", func(w http.ResponseWriter, r *http.Request) {
10301028
w.Header().Set("Content-Type", "text/plain")
1029+
state := p.State()
10311030
fmt.Fprintf(w,
10321031
"%d/%d, %.2f%%, %s left",
1033-
p.State().CurrentNum, p.State().Max, p.State().CurrentPercent*100,
1034-
(time.Second * time.Duration(p.State().SecondsLeft)).String(),
1032+
state.CurrentNum, state.Max, state.CurrentPercent*100,
1033+
(time.Second * time.Duration(state.SecondsLeft)).String(),
10351034
)
10361035
})
1037-
log.Fatal(http.ListenAndServe(hostPort, nil))
1036+
1037+
// create the server instance
1038+
server := &http.Server{
1039+
Addr: hostPort,
1040+
Handler: mux,
1041+
}
1042+
1043+
// start the server in a goroutine and ignore errors
1044+
go func() {
1045+
defer func() {
1046+
if err := recover(); err != nil {
1047+
fmt.Println("encounter panic: ", err)
1048+
}
1049+
}()
1050+
1051+
_ = server.ListenAndServe()
1052+
}()
1053+
1054+
// return the server instance for use by the caller
1055+
return server
10381056
}
10391057

10401058
// regex matching ansi escape codes

progressbar_test.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package progressbar
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/md5"
67
"encoding/hex"
78
"encoding/json"
@@ -471,7 +472,7 @@ func TestOptionSetTheme(t *testing.T) {
471472
bar.RenderBlank()
472473
result := strings.TrimSpace(buf.String())
473474
expect := "0% >----------<"
474-
if strings.Index(result, expect) == -1 {
475+
if !strings.Contains(result, expect) {
475476
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
476477
}
477478
buf.Reset()
@@ -487,7 +488,7 @@ func TestOptionSetTheme(t *testing.T) {
487488
bar.Finish()
488489
result = strings.TrimSpace(buf.String())
489490
expect = "100% >##########<"
490-
if strings.Index(result, expect) == -1 {
491+
if !strings.Contains(result, expect) {
491492
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
492493
}
493494
}
@@ -506,7 +507,7 @@ func TestOptionSetThemeFilled(t *testing.T) {
506507
bar.RenderBlank()
507508
result := strings.TrimSpace(buf.String())
508509
expect := "0% >----------<"
509-
if strings.Index(result, expect) == -1 {
510+
if !strings.Contains(result, expect) {
510511
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
511512
}
512513
buf.Reset()
@@ -522,7 +523,7 @@ func TestOptionSetThemeFilled(t *testing.T) {
522523
bar.Finish()
523524
result = strings.TrimSpace(buf.String())
524525
expect = "100% ]##########["
525-
if strings.Index(result, expect) == -1 {
526+
if !strings.Contains(result, expect) {
526527
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
527528
}
528529
}
@@ -1067,7 +1068,7 @@ func TestOptionSetSpinnerChangeIntervalZero(t *testing.T) {
10671068
bar.lock.Lock()
10681069
s, _ := vt.String()
10691070
bar.lock.Unlock()
1070-
s = strings.TrimSpace(s)
1071+
_ = strings.TrimSpace(s)
10711072
}
10721073
for i := range actuals {
10731074
assert.Equal(t, expected[i], actuals[i])
@@ -1130,7 +1131,7 @@ func TestStartHTTPServer(t *testing.T) {
11301131
bar.Add(1)
11311132

11321133
hostPort := "localhost:9696"
1133-
go bar.StartHTTPServer(hostPort)
1134+
svr := bar.StartHTTPServer(hostPort)
11341135

11351136
// check plain text
11361137
resp, err := http.Get(fmt.Sprintf("http://%s/desc", hostPort))
@@ -1162,4 +1163,19 @@ func TestStartHTTPServer(t *testing.T) {
11621163
if result.Max != bar.State().Max || result.CurrentNum != bar.State().CurrentNum {
11631164
t.Errorf("wrong state: %v", result)
11641165
}
1166+
1167+
// shutdown server
1168+
err = svr.Shutdown(context.Background())
1169+
if err != nil {
1170+
t.Errorf("shutdown server failed: %v", err)
1171+
}
1172+
1173+
// start new bar server
1174+
bar = Default(10, "test")
1175+
bar.Add(1)
1176+
svr = bar.StartHTTPServer(hostPort)
1177+
err = svr.Close()
1178+
if err != nil {
1179+
t.Errorf("shutdown server failed: %v", err)
1180+
}
11651181
}

0 commit comments

Comments
 (0)