Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fancy progress bar for updates #3

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"strings"

"github.com/jedib0t/go-pretty/v6/progress"
"github.com/spf13/cobra"
"github.com/ublue-os/uupd/checks"
"github.com/ublue-os/uupd/drv"
Expand Down Expand Up @@ -80,87 +81,107 @@ func Update(cmd *cobra.Command, args []string) {
}

totalSteps := brewUpdate + systemUpdate + 1 + len(users) + 1 + len(users) // system + Brew + Flatpak (users + root) + Distrobox (users + root)
currentStep := 0
pw := lib.NewProgressWriter()
pw.SetNumTrackersExpected(1)
go pw.Render()
// -1 because 0 index
tracker := lib.NewIncrementTracker(&progress.Tracker{Message: "Updating", Units: progress.UnitsDefault, Total: int64(totalSteps - 1)}, totalSteps-1)
pw.AppendTracker(tracker.Tracker)

failures := make(map[string]Failure)

if updateAvailable {
currentStep++
log.Printf("[%d/%d] Updating System (%s)", currentStep, totalSteps, systemDriver.Name)
tracker.IncrementSection()
lib.ChangeTrackerMessageFancy(pw, tracker, "Updating System")
out, err := systemDriver.Update()
if err != nil {
failures[systemDriver.Name] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
}

if brewUpdate == 1 {
currentStep++
log.Printf("[%d/%d] Updating CLI Apps (Brew)", currentStep, totalSteps)
lib.ChangeTrackerMessageFancy(pw, tracker, "Updating CLI apps (Brew)")
out, err := drv.BrewUpdate(brewUid)
if err != nil {
failures["Brew"] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
}

// Run flatpak updates
currentStep++
log.Printf("[%d/%d] Updating System Apps (Flatpak)", currentStep, totalSteps)
lib.ChangeTrackerMessageFancy(pw, tracker, "Updating System Apps (Flatpak)")
flatpakCmd := exec.Command("/usr/bin/flatpak", "update", "-y")
out, err := flatpakCmd.CombinedOutput()
if err != nil {
failures["Flatpak"] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
for _, user := range users {
currentStep++
log.Printf("[%d/%d] Updating Apps for User: %s (Flatpak)", currentStep, totalSteps, user.Name)
lib.ChangeTrackerMessageFancy(pw, tracker, fmt.Sprintf("Updating Apps for User: %s (Flatpak)", user.Name))
out, err := lib.RunUID(user.UID, []string{"/usr/bin/flatpak", "update", "-y"}, nil)
if err != nil {
failures[fmt.Sprintf("Flatpak User: %s", user.Name)] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
}

// Run distrobox updates
currentStep++
log.Printf("[%d/%d] Updating System Distroboxes", currentStep, totalSteps)
lib.ChangeTrackerMessageFancy(pw, tracker, "Updating System Distroboxes")
// distrobox doesn't support sudo, run with systemd-run
out, err = lib.RunUID(0, []string{"/usr/bin/distrobox", "upgrade", "-a"}, nil)
if err != nil {
failures["Distrobox"] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
for _, user := range users {
currentStep++
log.Printf("[%d/%d] Updating Distroboxes for User: %s", currentStep, totalSteps, user.Name)
lib.ChangeTrackerMessageFancy(pw, tracker, fmt.Sprintf("Updating Distroboxes for User: %s", user.Name))
out, err := lib.RunUID(user.UID, []string{"/usr/bin/distrobox", "upgrade", "-a"}, nil)
if err != nil {
failures[fmt.Sprintf("Distrobox User: %s", user.Name)] = Failure{
err,
string(out),
}
tracker.IncrementSectionError()
} else {
tracker.IncrementSection()
}
}

if len(failures) > 0 {
pw.SetAutoStop(false)
pw.Stop()
failedSystemsList := make([]string, 0, len(failures))
for systemName := range failures {
failedSystemsList = append(failedSystemsList, systemName)
}
failedSystemsStr := strings.Join(failedSystemsList, ", ")
lib.Notify("Updates failed", fmt.Sprintf("uupd failed to update: %s, consider seeing logs with `journalctl -exu uupd.service`", failedSystemsStr))

log.Printf("Updates Completed with Failures:")
for name, fail := range failures {
indentedOutput := "\t | "
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.9

require (
github.com/godbus/dbus/v5 v5.1.0
github.com/jedib0t/go-pretty/v6 v6.6.3
github.com/shirou/gopsutil/v4 v4.24.10
github.com/spf13/cobra v1.8.1
)
Expand All @@ -13,10 +14,13 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.17.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.6.3 h1:nGqgS0tgIO1Hto47HSaaK4ac/I/Bu7usmdD3qvs0WvM=
github.com/jedib0t/go-pretty/v6 v6.6.3/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
Expand All @@ -39,6 +45,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
84 changes: 84 additions & 0 deletions lib/percentmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package lib

import (
"github.com/jedib0t/go-pretty/v6/progress"
"github.com/jedib0t/go-pretty/v6/text"
"time"
)

type Incrementer struct {
doneIncrements int
MaxIncrements int
}

type IncrementTracker struct {
Tracker *progress.Tracker
incrementer *Incrementer
}

var CuteColors = progress.StyleColors{
Message: text.Colors{text.FgWhite},
Error: text.Colors{text.FgRed},
Percent: text.Colors{text.FgHiBlue},
Pinned: text.Colors{text.BgHiBlack, text.FgWhite, text.Bold},
Stats: text.Colors{text.FgHiBlack},
Time: text.Colors{text.FgBlue},
Tracker: text.Colors{text.FgHiBlue},
Value: text.Colors{text.FgBlue},
Speed: text.Colors{text.FgBlue},
}

func NewProgressWriter() progress.Writer {
pw := progress.NewWriter()
pw.SetTrackerLength(25)
pw.Style().Visibility.TrackerOverall = true
pw.Style().Visibility.Time = true
pw.Style().Visibility.Tracker = true
pw.Style().Visibility.Value = true
pw.SetMessageLength(32)
pw.SetSortBy(progress.SortByPercentDsc)
pw.SetStyle(progress.StyleBlocks)
pw.SetTrackerPosition(progress.PositionRight)
pw.SetUpdateFrequency(time.Millisecond * 100)
pw.Style().Options.PercentFormat = "%4.1f%%"
pw.Style().Colors = CuteColors
return pw
}

func NewIncrementTracker(tracker *progress.Tracker, max_increments int) *IncrementTracker {
return &IncrementTracker{
Tracker: tracker,
incrementer: &Incrementer{MaxIncrements: max_increments},
}
}

func ChangeTrackerMessageFancy(writer progress.Writer, tracker *IncrementTracker, message string) {
writer.SetMessageLength(len(message))
tracker.Tracker.UpdateMessage(message)
}

func (it *IncrementTracker) IncrementSection() {
var increment_step float64
if it.incrementer.doneIncrements == 0 {
increment_step = 1
} else {
increment_step = float64(it.Tracker.Total / int64(it.incrementer.MaxIncrements))
}
it.Tracker.Increment(int64(increment_step))
it.incrementer.doneIncrements++
}

func (it *IncrementTracker) IncrementSectionError() {
var increment_step float64
if it.incrementer.doneIncrements == 0 {
increment_step = 1
} else {
increment_step = float64(it.Tracker.Total / int64(it.incrementer.MaxIncrements))
}
it.Tracker.IncrementWithError(int64(increment_step))
it.incrementer.doneIncrements++
}

func (it *IncrementTracker) CurrentStep() int {
return it.incrementer.doneIncrements
}