Skip to content

Commit

Permalink
Prettier update progress tracker using bubbletea
Browse files Browse the repository at this point in the history
  • Loading branch information
DaltonSW committed Aug 16, 2024
1 parent e264d7f commit b6ca7df
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 2 deletions.
3 changes: 2 additions & 1 deletion cmd/aocli/aocli.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func main() {
Help(args)
return
} else if args[1] == "update" {
Update()
// Update()
RunUpdateModel()
return
} else if args[1] == "leaderboard" {
Leaderboard(args)
Expand Down
174 changes: 174 additions & 0 deletions cmd/aocli/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"strings"

"dalton.dog/aocgo/internal/styles"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"golang.org/x/mod/semver"
)
Expand Down Expand Up @@ -146,3 +149,174 @@ func Update() {

logger.Infof("Updated successfully to version %v", release.TagName)
}

type initMsg int
type doneMsg string
type urlMsg struct {
assetURL string
version string
}

type fileMsg struct {
curFile string
tmpFile *os.File
}

type errMsg struct{ err error }

type updateModel struct {
spinner spinner.Model
status string
done bool
err error

version string
assetURL string
curFile string
tmpFile *os.File
}

func RunUpdateModel() {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(styles.UpdateSpinnerColor))

model := updateModel{
spinner: s,
status: "Starting up!",
done: false,
}

p := tea.NewProgram(model)

if _, err := p.Run(); err != nil {
log.Fatal(err)
}

}

func (m updateModel) Init() tea.Cmd {
initCmd := func() tea.Msg { return initMsg(1) }

return tea.Batch(initCmd, m.spinner.Tick)
}

func (m updateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case initMsg:
m.status = "Querying for latest release"
cmds = append(cmds, func() tea.Msg {
release, err := getLatestRelease()
if err != nil {
return errMsg{err}
}

var assetURL string
for _, asset := range release.Assets {
if asset.Name == fmt.Sprintf("aocli-%v-%v", runtime.GOOS, runtime.GOARCH) {
assetURL = asset.DownloadURL
} else if asset.Name == fmt.Sprintf("aocli-%v-%v.exe", runtime.GOOS, runtime.GOARCH) {
assetURL = asset.DownloadURL
}
}

if assetURL == "" {
return errMsg{err}
}

return urlMsg{assetURL: assetURL, version: release.TagName}
})

case urlMsg:
m.status = "Downloading latest release to temp file"
url := msg.assetURL
m.version = msg.version

cmds = append(cmds, urlCmd(url))

case fileMsg:
m.status = "Replacing current file with new version, then cleaning up"
cur := msg.curFile
tmp := msg.tmpFile

cmds = append(cmds, fileCmd(cur, tmp))
case doneMsg:
m.done = true
m.status = fmt.Sprintf("Successfully updated to version %s", m.version)
return m, tea.Quit
}

var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
cmds = append(cmds, cmd)

return m, tea.Batch(cmds...)
}

func (m updateModel) View() string {
var symbol string
var status string

if m.err != nil {
symbol = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Render("")
status = m.err.Error()
} else if m.done {
symbol = lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00")).Render("󰸞")
status = m.status
} else {
symbol = m.spinner.View()
status = m.status
}

return fmt.Sprintf("\n %s %s\n", symbol, status)
}

func urlCmd(assetURL string) tea.Cmd {
return func() tea.Msg {
resp, err := http.Get(assetURL)
if err != nil {
return errMsg{err}
}
defer resp.Body.Close()

curExec, err := os.Executable()
if err != nil {
return errMsg{err}
}

tmpFile, err := os.CreateTemp("", "aocli-update-")
if err != nil {
return errMsg{err}
}

// Write the downloaded content to the temporary file
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
return errMsg{err}
}

return fileMsg{curFile: curExec, tmpFile: tmpFile}
}
}

func fileCmd(curFile string, tmpFile *os.File) tea.Cmd {
return func() tea.Msg {
// Close the file to flush the content
if err := tmpFile.Close(); err != nil {
return errMsg{err}
}

// Make the temp file executable
if err := os.Chmod(tmpFile.Name(), 0700); err != nil {
return errMsg{err}
}

// Replace the current executable with the new one
if err := os.Rename(tmpFile.Name(), curFile); err != nil {
return errMsg{err}
}

os.Remove(tmpFile.Name())
return doneMsg(1)

Check failure on line 320 in cmd/aocli/updater.go

View workflow job for this annotation

GitHub Actions / build

conversion from untyped int to doneMsg (string) yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)

Check failure on line 320 in cmd/aocli/updater.go

View workflow job for this annotation

GitHub Actions / Publish for ubuntu-latest

conversion from untyped int to doneMsg (string) yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/charmbracelet/bubbletea v0.26.6
github.com/charmbracelet/lipgloss v0.12.1
github.com/charmbracelet/log v0.4.0
github.com/evertras/bubble-table v0.16.1
go.etcd.io/bbolt v1.3.10
golang.org/x/mod v0.20.0
golang.org/x/term v0.23.0
Expand All @@ -28,7 +29,6 @@ require (
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.2 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/evertras/bubble-table v0.16.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down
2 changes: 2 additions & 0 deletions internal/styles/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ var (
starColor = lipgloss.AdaptiveColor{Light: "#F1FA8C", Dark: "#F1FA8C"}
linkColor = lipgloss.AdaptiveColor{Light: "#8BE9FD", Dark: "#8BE9FD"}
codeColor = lipgloss.AdaptiveColor{Light: "#FAC3D5", Dark: "#FAC3D5"}

UpdateSpinnerColor = lipgloss.Color("#FB25A0")
)

var (
Expand Down

0 comments on commit b6ca7df

Please sign in to comment.