Skip to content

Commit aaf4ab7

Browse files
committed
chore: implement progress bars
1 parent 315d21c commit aaf4ab7

7 files changed

Lines changed: 163 additions & 85 deletions

File tree

go.mod

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
module github.com/fxManagerProject/cli-installer
22

3-
go 1.25.0
3+
go 1.25
44

55
require (
6-
charm.land/bubbles/v2 v2.1.1
7-
charm.land/bubbletea/v2 v2.0.8
8-
charm.land/lipgloss/v2 v2.0.5
6+
charm.land/bubbles/v2 v2.0.0
7+
charm.land/bubbletea/v2 v2.0.0
8+
charm.land/lipgloss/v2 v2.0.0
99
github.com/ulikunitz/xz v0.5.15
1010
)
1111

1212
require (
13-
github.com/charmbracelet/colorprofile v0.4.3 // indirect
14-
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
15-
github.com/charmbracelet/x/ansi v0.11.7 // indirect
13+
github.com/charmbracelet/colorprofile v0.4.2 // indirect
14+
github.com/charmbracelet/harmonica v0.2.0 // indirect
15+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
16+
github.com/charmbracelet/x/ansi v0.11.6 // indirect
1617
github.com/charmbracelet/x/term v0.2.2 // indirect
1718
github.com/charmbracelet/x/termios v0.1.1 // indirect
1819
github.com/charmbracelet/x/windows v0.2.2 // indirect
1920
github.com/clipperhouse/displaywidth v0.11.0 // indirect
2021
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
21-
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
22-
github.com/mattn/go-runewidth v0.0.24 // indirect
22+
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
23+
github.com/mattn/go-runewidth v0.0.20 // indirect
2324
github.com/muesli/cancelreader v0.2.2 // indirect
2425
github.com/rivo/uniseg v0.4.7 // indirect
2526
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
26-
golang.org/x/sync v0.21.0 // indirect
27-
golang.org/x/sys v0.46.0 // indirect
27+
golang.org/x/sync v0.19.0 // indirect
28+
golang.org/x/sys v0.41.0 // indirect
2829
)

go.sum

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
2-
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
3-
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
4-
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
5-
charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
6-
charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
7-
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
8-
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
9-
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
10-
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
11-
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 h1:3FmWoGNWK4STvqg0O0Aeav2T7rodWJAPeF0QpH+8gFw=
12-
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7/go.mod h1:f/jRa757WUmaOZrbPspXymbg/GnbF+rwe4OLsG7aXYo=
13-
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
14-
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
1+
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
2+
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
3+
charm.land/bubbletea/v2 v2.0.0 h1:p0d6CtWyJXJ9GfzMpUUqbP/XUUhhlk06+vCKWmox1wQ=
4+
charm.land/bubbletea/v2 v2.0.0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
5+
charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
6+
charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
7+
github.com/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM=
8+
github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
9+
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
10+
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
11+
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
12+
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
13+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
14+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
15+
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
16+
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
1517
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
1618
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
1719
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
@@ -24,10 +26,10 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
2426
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
2527
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
2628
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
27-
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
28-
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
29-
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
30-
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
29+
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
30+
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
31+
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
32+
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
3133
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
3234
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
3335
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -38,7 +40,7 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
3840
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
3941
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
4042
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
41-
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
42-
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
43-
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
44-
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
43+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
44+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
45+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
46+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=

internal/archive/extract.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import (
2121
var httpClient = &http.Client{Timeout: 5 * time.Minute}
2222

2323
// DownloadToTemp streams a URL to a temp file and returns its path.
24-
// The caller is responsible for removing it
25-
func DownloadToTemp(url string) (string, error) {
24+
func DownloadToTemp(url string, onProgress func(ratio float64)) (string, error) {
2625
req, err := http.NewRequest(http.MethodGet, url, nil)
2726
if err != nil {
2827
return "", err
@@ -45,13 +44,43 @@ func DownloadToTemp(url string) (string, error) {
4544
}
4645
defer f.Close()
4746

48-
if _, err := io.Copy(f, resp.Body); err != nil {
47+
var body io.Reader = resp.Body
48+
if onProgress != nil && resp.ContentLength > 0 {
49+
body = &progressReader{r: resp.Body, total: resp.ContentLength, onProgress: onProgress}
50+
}
51+
52+
if _, err := io.Copy(f, body); err != nil {
4953
os.Remove(f.Name())
5054
return "", fmt.Errorf("writing download to temp file: %w", err)
5155
}
5256
return f.Name(), nil
5357
}
5458

59+
// progressReader wraps an io.Reader and reports cumulative bytes read
60+
// as a 0.0-1.0 ratio of total. Reports are throttled to whole
61+
// percentage-point changes so a fast local network doesn't flood the
62+
// UI layer with updates it can't render fast enough anyway.
63+
type progressReader struct {
64+
r io.Reader
65+
total int64
66+
read int64
67+
lastPct int
68+
onProgress func(ratio float64)
69+
}
70+
71+
func (p *progressReader) Read(buf []byte) (int, error) {
72+
n, err := p.r.Read(buf)
73+
if n > 0 {
74+
p.read += int64(n)
75+
pct := int(float64(p.read) / float64(p.total) * 100)
76+
if pct != p.lastPct {
77+
p.lastPct = pct
78+
p.onProgress(float64(p.read) / float64(p.total))
79+
}
80+
}
81+
return n, err
82+
}
83+
5584
// ExtractAuto picks zip or tar.gz extraction based on the URL/filename
5685
// suffix and extracts into destDir (created if missing)
5786
func ExtractAuto(archivePath, sourceURLOrName, destDir string) error {

internal/downloader/download.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414
// Progress is an optional callback for reporting install progress back
1515
// to a UI layer (TUI, plain stdout, whatever)
1616
type Progress struct {
17-
OnStart func(url string)
18-
OnDone func(url, destDir string)
17+
OnStart func(url string)
18+
OnProgress func(ratio float64) // called repeatedly during download, 0.0-1.0; may never fire if the server omits Content-Length
19+
OnDone func(url, destDir string)
1920
}
2021

2122
// DownloadAndExtract downloads the file at url and extracts it into
@@ -29,7 +30,7 @@ func DownloadAndExtract(url, destDir string, filenameHint string, progress *Prog
2930
progress.OnStart(url)
3031
}
3132

32-
tmpFile, err := archive.DownloadToTemp(url)
33+
tmpFile, err := archive.DownloadToTemp(url, progressCallback(progress))
3334
if err != nil {
3435
return fmt.Errorf("downloading %s: %w", url, err)
3536
}
@@ -50,6 +51,15 @@ func DownloadAndExtract(url, destDir string, filenameHint string, progress *Prog
5051
return nil
5152
}
5253

54+
// progressCallback safely extracts OnProgress from a possibly-nil
55+
// Progress so callers of archive.DownloadToTemp never need a nil check.
56+
func progressCallback(p *Progress) func(ratio float64) {
57+
if p == nil {
58+
return nil
59+
}
60+
return p.OnProgress
61+
}
62+
5363
// DownloadAndExtractToTemp downloads and extracts into a freshly
5464
// created temp directory, returning its path. Useful when the caller
5565
// needs to inspect or selectively copy extracted contents (e.g. pulling

internal/layout/scaffold.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,14 @@ func (p *Paths) RemoveTxAdminResource() error {
108108
if err != nil {
109109
return err
110110
}
111-
112-
dest := filepath.Join(sysRes, "monitor")
113-
return os.RemoveAll(dest)
111+
monitorDir := filepath.Join(sysRes, "monitor")
112+
if _, err := os.Stat(monitorDir); os.IsNotExist(err) {
113+
return nil
114+
}
115+
if err := os.RemoveAll(monitorDir); err != nil {
116+
return fmt.Errorf("removing stock monitor resource at %s: %w", monitorDir, err)
117+
}
118+
return nil
114119
}
115120

116121
// copyTree recursively copies src into dst (dst is created if needed)

internal/platform/detect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func ParseOverride(raw string) (Target, error) {
5555
// ghrelease.Release.FindAsset) that identifies the fxManager webpanel
5656
// binary+assets archive for this target among a release's assets.
5757
// fxManager ships one archive per OS (e.g. containing
58-
// "fxmanager-windows.exe" + "public/"), named to include the OS
58+
// "fxmanager-windows.exe" + "assets/"), named to include the OS
5959
func (t Target) FxManagerAssetPattern() string {
6060
return string(t)
6161
}

0 commit comments

Comments
 (0)