Commit be23b5bf authored by Roman Alifanov's avatar Roman Alifanov

Fix update flow: continue on play errors, add progress label and finish log

parent 1f6fe85e
......@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"log"
"strings"
"sync"
"time"
)
......@@ -146,22 +147,24 @@ func (us *UpdateService) RunUpdates(ctx context.Context) error {
startTime := time.Now()
packagesUpdated := 0
var updateErr error
var categoryErrors []string
systemOrKernelSucceeded := false
// Execute updates sequentially
for _, category := range categories {
if err := us.runCategoryUpdate(ctx, category, transaction); err != nil {
updateErr = err
log.Printf("Update failed for %s: %v", category, err)
break
categoryErrors = append(categoryErrors, fmt.Sprintf("%s: %v", category, err))
continue
}
// Count packages
switch category {
case "System":
packagesUpdated += len(state.SystemUpdates.Packages)
systemOrKernelSucceeded = true
case "Kernel":
packagesUpdated += len(state.KernelUpdates.Packages)
systemOrKernelSucceeded = true
}
}
......@@ -169,10 +172,11 @@ func (us *UpdateService) RunUpdates(ctx context.Context) error {
close(progressDone)
// Finish update
success := updateErr == nil
success := len(categoryErrors) == 0
us.store.Dispatch(&store.FinishUpdateAction{
Success: success,
Error: fmt.Sprintf("%v", updateErr),
Success: success,
Error: strings.Join(categoryErrors, "; "),
SystemOrKernelSucceeded: systemOrKernelSucceeded,
})
// Record in history
......@@ -190,14 +194,14 @@ func (us *UpdateService) RunUpdates(ctx context.Context) error {
}
if !success {
entry.ErrorMessage = fmt.Sprintf("%v", updateErr)
entry.ErrorMessage = strings.Join(categoryErrors, "; ")
}
us.history.RecordUpdate(entry)
}
if updateErr != nil {
return updateErr
if !success {
return fmt.Errorf("%s", strings.Join(categoryErrors, "; "))
}
log.Println("Update completed successfully")
......
......@@ -124,8 +124,9 @@ func (a *UpdateProgressAction) Apply(state *State) error {
}
type FinishUpdateAction struct {
Success bool
Error string
Success bool
Error string
SystemOrKernelSucceeded bool
}
func (a *FinishUpdateAction) Apply(state *State) error {
......
......@@ -19,6 +19,15 @@ Adw.NavigationPage process_page {
orientation: vertical;
spacing: 12;
Label progress_label {
label: _("");
ellipsize: end;
styles [
"caption",
]
}
ProgressBar progress_bar {}
ListBox {
......@@ -52,20 +61,53 @@ Adw.NavigationPage process_page {
}
StackPage {
child: Adw.StatusPage {
child: Adw.StatusPage finish_status_page {
description: _("Click on button below to restart device");
icon-name: "face-smile-big-symbolic";
title: _("Updating complete!");
Button restart_button {
halign: center;
hexpand: false;
label: _("Reboot");
Adw.Clamp {
maximum-size: 500;
Box {
orientation: vertical;
spacing: 12;
Button restart_button {
halign: center;
hexpand: false;
label: _("Reboot");
styles [
"pill",
"suggested-action",
]
}
ListBox {
selection-mode: none;
styles [
"pill",
"suggested-action",
]
Adw.ExpanderRow {
title: _("Show more information");
ScrolledWindow {
height-request: 300;
hscrollbar-policy: never;
propagate-natural-height: true;
TextView finish_log_view {
cursor-visible: false;
editable: false;
wrap-mode: word_char;
}
}
}
styles [
"boxed-list",
]
}
}
}
};
......
......@@ -28,6 +28,15 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<property name="orientation">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="progress_label">
<property name="label" translatable="yes"></property>
<property name="ellipsize">3</property>
<style>
<class name="caption"/>
</style>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar"></object>
</child>
<child>
......@@ -69,19 +78,57 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child>
<object class="GtkStackPage">
<property name="child">
<object class="AdwStatusPage">
<object class="AdwStatusPage" id="finish_status_page">
<property name="description" translatable="yes">Click on button below to restart device</property>
<property name="icon-name">face-smile-big-symbolic</property>
<property name="title" translatable="yes">Updating complete!</property>
<child>
<object class="GtkButton" id="restart_button">
<property name="halign">3</property>
<property name="hexpand">false</property>
<property name="label" translatable="yes">Reboot</property>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
<object class="AdwClamp">
<property name="maximum-size">500</property>
<child>
<object class="GtkBox">
<property name="orientation">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="restart_button">
<property name="halign">3</property>
<property name="hexpand">false</property>
<property name="label" translatable="yes">Reboot</property>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="selection-mode">0</property>
<child>
<object class="AdwExpanderRow">
<property name="title" translatable="yes">Show more information</property>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">300</property>
<property name="hscrollbar-policy">2</property>
<property name="propagate-natural-height">true</property>
<child>
<object class="GtkTextView" id="finish_log_view">
<property name="cursor-visible">false</property>
<property name="editable">false</property>
<property name="wrap-mode">3</property>
</object>
</child>
</object>
</child>
</object>
</child>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
......
......@@ -6,6 +6,7 @@ import (
"SystemUpdater/model"
"github.com/diamondburned/gotk4-adwaita/pkg/adw"
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
gtksbuilder "SystemUpdater/lib/gtks/builder"
......@@ -17,13 +18,18 @@ import (
var processPageUI string
type UpdateProcessPage struct {
NavigationPage *adw.NavigationPage `gtk:"process_page"`
stack *gtk.Stack `gtk:"process_stack"`
statusPage *adw.StatusPage `gtk:"status_page"`
progressBar *gtk.ProgressBar `gtk:"progress_bar"`
logView *gtk.TextView `gtk:"log_view"`
logBuffer *gtk.TextBuffer
rebootBtn *gtk.Button `gtk:"restart_button"`
NavigationPage *adw.NavigationPage `gtk:"process_page"`
stack *gtk.Stack `gtk:"process_stack"`
statusPage *adw.StatusPage `gtk:"status_page"`
finishStatusPage *adw.StatusPage `gtk:"finish_status_page"`
progressBar *gtk.ProgressBar `gtk:"progress_bar"`
progressLabel *gtk.Label `gtk:"progress_label"`
logView *gtk.TextView `gtk:"log_view"`
logBuffer *gtk.TextBuffer
finishLogView *gtk.TextView `gtk:"finish_log_view"`
finishLogBuffer *gtk.TextBuffer
rebootBtn *gtk.Button `gtk:"restart_button"`
pulseSourceID glib.SourceHandle
}
func NewUpdateProcessPage(onReboot func()) *UpdateProcessPage {
......@@ -35,6 +41,7 @@ func NewUpdateProcessPage(onReboot func()) *UpdateProcessPage {
}
page.logBuffer = page.logView.Buffer()
page.finishLogBuffer = page.finishLogView.Buffer()
page.progressBar.SetShowText(true)
page.rebootBtn.ConnectClicked(func() {
......@@ -48,30 +55,52 @@ func NewUpdateProcessPage(onReboot func()) *UpdateProcessPage {
func (p *UpdateProcessPage) UpdateProgress(event model.EventData) {
if event.Progress > 0 {
p.stopPulse()
p.progressBar.SetFraction(event.Progress / 100.0)
} else if p.pulseSourceID == 0 {
p.startPulse()
}
if event.Message != "" && event.EventType != "PROGRESS" {
endIter := p.logBuffer.EndIter()
p.logBuffer.Insert(endIter, event.Message+"\n")
if event.Message != "" {
p.progressLabel.SetLabel(event.Message)
mark := p.logBuffer.CreateMark("end", endIter, false)
p.logView.ScrollToMark(mark, 0.0, false, 0.0, 0.0)
if event.EventType != "PROGRESS" {
endIter := p.logBuffer.EndIter()
p.logBuffer.Insert(endIter, event.Message+"\n")
mark := p.logBuffer.CreateMark("end", endIter, false)
p.logView.ScrollToMark(mark, 0.0, false, 0.0, 0.0)
}
}
}
func (p *UpdateProcessPage) ShowComplete(success bool) {
func (p *UpdateProcessPage) ShowComplete(success bool, showReboot bool) {
if success {
p.statusPage.SetTitle("Updating complete!")
p.statusPage.SetIconName("face-smile-big-symbolic")
p.statusPage.SetDescription("Click on button below to restart device")
p.finishStatusPage.SetTitle("Updating complete!")
p.finishStatusPage.SetIconName("face-smile-big-symbolic")
p.finishStatusPage.SetDescription("Click on button below to restart device")
} else if showReboot {
p.finishStatusPage.SetTitle("Update Partially Complete")
p.finishStatusPage.SetIconName("dialog-warning-symbolic")
p.finishStatusPage.SetDescription("Some updates failed, but system packages were updated. A reboot is recommended.")
} else {
p.statusPage.SetTitle("Update Failed")
p.statusPage.SetIconName("dialog-error-symbolic")
p.statusPage.SetDescription("The update encountered an error. Please check the logs.")
p.rebootBtn.SetVisible(false)
p.finishStatusPage.SetTitle("Update Failed")
p.finishStatusPage.SetIconName("dialog-error-symbolic")
p.finishStatusPage.SetDescription("The update encountered an error. Please check the logs.")
}
p.rebootBtn.SetVisible(showReboot || success)
// Copy log to finish page
start := p.logBuffer.StartIter()
end := p.logBuffer.EndIter()
logText := p.logBuffer.Text(start, end, false)
if logText != "" {
finishEnd := p.finishLogBuffer.EndIter()
p.finishLogBuffer.Insert(finishEnd, logText)
}
p.stopPulse()
p.stack.SetVisibleChildName("finish")
}
......@@ -79,7 +108,28 @@ func (p *UpdateProcessPage) Reset() {
start := p.logBuffer.StartIter()
end := p.logBuffer.EndIter()
p.logBuffer.Delete(start, end)
finishStart := p.finishLogBuffer.StartIter()
finishEnd := p.finishLogBuffer.EndIter()
p.finishLogBuffer.Delete(finishStart, finishEnd)
p.progressBar.SetFraction(0.0)
p.progressLabel.SetLabel("")
p.stopPulse()
p.stack.SetVisibleChildName("default")
p.rebootBtn.SetVisible(true)
}
func (p *UpdateProcessPage) startPulse() {
p.pulseSourceID = glib.TimeoutAdd(100, func() bool {
p.progressBar.Pulse()
return true
})
}
func (p *UpdateProcessPage) stopPulse() {
if p.pulseSourceID != 0 {
glib.SourceRemove(p.pulseSourceID)
p.pulseSourceID = 0
}
}
......@@ -135,7 +135,8 @@ func (w *Window) onStateChange(change store.StateChange) {
case store.ChangeUpdateFinish:
action, ok := change.Data.(*store.FinishUpdateAction)
if ok {
w.processPage.ShowComplete(action.Success)
showReboot := action.Success || action.SystemOrKernelSucceeded
w.processPage.ShowComplete(action.Success, showReboot)
}
case store.ChangeHistory:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment