diff --git a/cmd/update.go b/cmd/update.go index c763854..8ca34d7 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -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" @@ -80,36 +81,46 @@ 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 { @@ -117,22 +128,26 @@ func Update(cmd *cobra.Command, args []string) { 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 { @@ -140,27 +155,33 @@ func Update(cmd *cobra.Command, args []string) { 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 | " diff --git a/go.mod b/go.mod index f8ae2d3..b2caf2e 100644 --- a/go.mod +++ b/go.mod @@ -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 ) @@ -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 ) diff --git a/go.sum b/go.sum index c09fbd9..451ba8d 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/lib/percentmanager.go b/lib/percentmanager.go new file mode 100644 index 0000000..e07820f --- /dev/null +++ b/lib/percentmanager.go @@ -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 +}