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