Skip to content

Commit a163f65

Browse files
committed
feat: simple downloader
1 parent 9d3055a commit a163f65

File tree

6 files changed

+163
-14
lines changed

6 files changed

+163
-14
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ Thumbs.db
1818

1919
# GoLand specific files
2020
*.iml
21+
22+
*.weave

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/initia-labs/weave
33
go 1.22.6
44

55
require (
6+
github.com/charmbracelet/bubbles v0.20.0
67
github.com/charmbracelet/bubbletea v1.1.0
78
github.com/charmbracelet/lipgloss v0.13.0
89
github.com/spf13/cobra v1.8.1
@@ -12,6 +13,7 @@ require (
1213

1314
require (
1415
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
16+
github.com/charmbracelet/harmonica v0.2.0 // indirect
1517
github.com/charmbracelet/x/ansi v0.2.3 // indirect
1618
github.com/charmbracelet/x/term v0.2.0 // indirect
1719
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
22
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
3+
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
4+
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
35
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
46
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
7+
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
8+
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
59
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
610
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
711
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
@@ -13,6 +17,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
1317
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1418
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
1519
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
20+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
21+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
1622
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
1723
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
1824
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=

models/weaveinit/run_l1_node.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -869,34 +869,34 @@ func (m *StateSyncEndpointInput) View() string {
869869
}
870870

871871
type SnapshotDownloadLoading struct {
872-
utils.Loading
872+
utils.Downloader
873873
state *RunL1NodeState
874874
}
875875

876876
func NewSnapshotDownloadLoading(state *RunL1NodeState) *SnapshotDownloadLoading {
877877
return &SnapshotDownloadLoading{
878-
Loading: utils.NewLoading("Downloading snapshot from the provided URL...", utils.DefaultWait()),
879-
state: state,
878+
Downloader: *utils.NewDownloader(
879+
"Downloading snapshot from the provided URL",
880+
state.snapshotEndpoint,
881+
"snapshot.weave",
882+
),
883+
state: state,
880884
}
881885
}
882886

883887
func (m *SnapshotDownloadLoading) Init() tea.Cmd {
884-
return m.Loading.Init()
888+
return m.Downloader.Init()
885889
}
886890

887891
func (m *SnapshotDownloadLoading) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
888-
loader, cmd := m.Loading.Update(msg)
889-
m.Loading = loader
890-
if m.Loading.Completing {
891-
m.state.weave.PreviousResponse += styles.RenderPreviousResponse(styles.NoSeparator, "Snapshot downloaded successfully.", []string{}, "")
892+
if m.GetCompletion() {
892893
return m, tea.Quit
893894
}
895+
downloader, cmd := m.Downloader.Update(msg)
896+
m.Downloader = *downloader
894897
return m, cmd
895898
}
896899

897900
func (m *SnapshotDownloadLoading) View() string {
898-
if m.Completing {
899-
return m.state.weave.PreviousResponse
900-
}
901-
return m.state.weave.PreviousResponse + m.Loading.View()
901+
return m.state.weave.PreviousResponse + m.Downloader.View()
902902
}

styles/text.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ var (
6464
QuestionMark string = Text("? ", Cyan)
6565
CorrectMark string = Text("✓ ", Green)
6666
InformationMark string = Text("i ", Cyan)
67-
SelectorCursor string = Text("> ", Yellow)
67+
SelectorCursor string = Text("> ", Cyan)
6868
)
6969

7070
// RenderPrompt highlights phrases in the text if they match any phrase in the highlights list
7171
func RenderPrompt(text string, highlights []string, status PromptStatus) string {
72-
prompt := "\n"
72+
prompt := ""
7373
switch status {
7474
case Question:
7575
prompt += QuestionMark

utils/downloader.go

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"os"
8+
"time"
9+
10+
"github.com/charmbracelet/bubbles/progress"
11+
tea "github.com/charmbracelet/bubbletea"
12+
13+
"github.com/initia-labs/weave/styles"
14+
)
15+
16+
type Downloader struct {
17+
progress progress.Model
18+
total int64
19+
current int64
20+
text string
21+
url string
22+
dest string
23+
done bool
24+
}
25+
26+
func NewDownloader(text, url, dest string) *Downloader {
27+
return &Downloader{
28+
progress: progress.New(progress.WithGradient(string(styles.Cyan), "#008B8B")),
29+
total: 0,
30+
text: text,
31+
url: url,
32+
dest: dest,
33+
}
34+
}
35+
36+
func (m *Downloader) startDownload() tea.Cmd {
37+
return func() tea.Msg {
38+
// Use a high buffer size for faster I/O operations
39+
const bufferSize = 8192 // 8 KB buffer
40+
41+
resp, err := http.Get(m.url)
42+
if err != nil {
43+
m.done = true
44+
return nil
45+
}
46+
defer resp.Body.Close()
47+
48+
m.total = resp.ContentLength
49+
if m.total <= 0 {
50+
m.total = 1
51+
}
52+
53+
file, err := os.Create(m.dest)
54+
if err != nil {
55+
m.done = true
56+
return nil
57+
}
58+
defer file.Close()
59+
60+
buffer := make([]byte, bufferSize)
61+
var totalDownloaded int64
62+
for {
63+
n, err := resp.Body.Read(buffer)
64+
if err != nil && err != io.EOF {
65+
m.done = true
66+
return nil
67+
}
68+
if n == 0 {
69+
break
70+
}
71+
72+
// Write to file and update progress in a single step
73+
if _, err := file.Write(buffer[:n]); err != nil {
74+
m.done = true
75+
return nil
76+
}
77+
78+
totalDownloaded += int64(n)
79+
m.current = totalDownloaded
80+
81+
// Simulate network delay for smoother updates
82+
time.Sleep(10 * time.Millisecond)
83+
}
84+
85+
m.done = true
86+
return nil
87+
}
88+
}
89+
90+
func (m *Downloader) Init() tea.Cmd {
91+
return tea.Batch(m.tick(), m.startDownload())
92+
}
93+
94+
func (m *Downloader) Update(msg tea.Msg) (*Downloader, tea.Cmd) {
95+
switch msg := msg.(type) {
96+
case TickMsg:
97+
return m, m.tick()
98+
99+
case tea.KeyMsg:
100+
if msg.String() == "q" || msg.String() == "ctrl+c" {
101+
return m, tea.Quit
102+
}
103+
}
104+
105+
model, cmd := m.progress.Update(msg)
106+
m.progress = model.(progress.Model)
107+
return m, cmd
108+
}
109+
110+
func (m *Downloader) View() string {
111+
if m.done {
112+
return fmt.Sprintf("%sDownload Complete!\nTotal Size: %d bytes\n", styles.CorrectMark, m.total)
113+
}
114+
percentage := float64(m.current) / float64(m.total)
115+
return fmt.Sprintf("%s: %s / %s \n%s", m.text, ByteCountSI(m.current), ByteCountSI(m.total), m.progress.ViewAs(percentage))
116+
}
117+
118+
func (m *Downloader) GetCompletion() bool {
119+
return m.done
120+
}
121+
122+
func (m *Downloader) tick() tea.Cmd {
123+
return tea.Tick(100*time.Millisecond, func(t time.Time) tea.Msg {
124+
return TickMsg(t)
125+
})
126+
}
127+
128+
func ByteCountSI(b int64) string {
129+
const unit = 1000
130+
if b < unit {
131+
return fmt.Sprintf("%d B", b)
132+
}
133+
div, exp := int64(unit), 0
134+
for n := b / unit; n >= unit; n /= unit {
135+
div *= unit
136+
exp++
137+
}
138+
return fmt.Sprintf("%.3f %cB", float64(b)/float64(div), "kMGTPE"[exp])
139+
}

0 commit comments

Comments
 (0)