219 lines
4.5 KiB
Go
219 lines
4.5 KiB
Go
package loader
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"galaxy/client/updater"
|
|
"galaxy/connector"
|
|
"galaxy/storage"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/theme"
|
|
"fyne.io/fyne/v2/widget"
|
|
)
|
|
|
|
const (
|
|
loaderLogViewportColumns = 80
|
|
loaderLogViewportRows = 12
|
|
)
|
|
|
|
type loader struct {
|
|
app fyne.App
|
|
storage storage.Storage
|
|
connector connector.Connector
|
|
updater *updater.Manager
|
|
runner uiRunner
|
|
debugWindow fyne.Window
|
|
textGrid *widget.TextGrid
|
|
btn *widget.Button
|
|
|
|
ctx context.Context
|
|
|
|
resultMu sync.Mutex
|
|
result error
|
|
|
|
closeMu sync.Mutex
|
|
closeQuits bool
|
|
}
|
|
|
|
// loaderLogViewportMinSize derives a stable monospace TextGrid viewport size
|
|
// from the active Fyne text metrics.
|
|
func loaderLogViewportMinSize(app fyne.App) fyne.Size {
|
|
if app == nil || app.Driver() == nil {
|
|
return fyne.NewSize(0, 0)
|
|
}
|
|
|
|
cellSize, _ := app.Driver().RenderedTextSize(
|
|
"M",
|
|
theme.TextSize(),
|
|
fyne.TextStyle{Monospace: true},
|
|
nil,
|
|
)
|
|
|
|
return fyne.NewSize(
|
|
cellSize.Width*loaderLogViewportColumns,
|
|
cellSize.Height*loaderLogViewportRows,
|
|
)
|
|
}
|
|
|
|
func NewLoader(s storage.Storage, conn connector.Connector, app fyne.App) (*loader, error) {
|
|
l := &loader{
|
|
app: app,
|
|
connector: conn,
|
|
storage: s,
|
|
updater: updater.NewManager(s, conn),
|
|
runner: execRunner{},
|
|
textGrid: widget.NewTextGrid(),
|
|
debugWindow: app.NewWindow("Loader"),
|
|
}
|
|
l.btn = widget.NewButton("Retry", l.onButtonAction)
|
|
l.btn.Disable()
|
|
l.textGrid.Scroll = fyne.ScrollNone
|
|
l.debugWindow.SetCloseIntercept(l.onWindowClose)
|
|
|
|
logScroll := container.NewScroll(l.textGrid)
|
|
logScroll.Direction = container.ScrollBoth
|
|
logScroll.SetMinSize(loaderLogViewportMinSize(app))
|
|
|
|
actionBar := container.NewCenter(container.NewHBox(l.btn))
|
|
|
|
content := container.NewBorder(nil, actionBar, nil, nil, logScroll)
|
|
l.debugWindow.SetContent(content)
|
|
l.debugWindow.Resize(content.MinSize())
|
|
l.debugWindow.SetFixedSize(true)
|
|
l.debugWindow.CenterOnScreen()
|
|
|
|
return l, nil
|
|
}
|
|
|
|
func (l *loader) runOnce(ctx context.Context) error {
|
|
target, err := l.updater.EnsureLaunchTarget()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.logText(fmt.Sprintf("Starting UI client v%s", target.Version))
|
|
l.logText(fmt.Sprintf("Executable: %s", target.Path))
|
|
|
|
fyne.Do(func() {
|
|
l.debugWindow.Hide()
|
|
})
|
|
|
|
exitCode, runErr := l.runner.Run(ctx, target.Path)
|
|
markErr := l.updater.MarkLaunchResult(target.Version, exitCode, runErr)
|
|
|
|
switch {
|
|
case runErr != nil:
|
|
return errors.Join(fmt.Errorf("launch UI client v%s: %w", target.Version, runErr), markErr)
|
|
case exitCode != 0:
|
|
return errors.Join(fmt.Errorf("UI client v%s exited with code %d", target.Version, exitCode), markErr)
|
|
default:
|
|
return markErr
|
|
}
|
|
}
|
|
|
|
// init prepares and launches the standalone UI client, or shows a retry button on failure.
|
|
func (l *loader) init(ctx context.Context) {
|
|
l.setCloseQuits(false)
|
|
fyne.Do(func() {
|
|
l.textGrid.SetText("")
|
|
l.btn.Hide()
|
|
l.btn.Disable()
|
|
// show debugWindow can be done with future debug mode, e.g. with -debug flag
|
|
// l.window.Show()
|
|
})
|
|
|
|
err := l.runOnce(ctx)
|
|
if err == nil || errors.Is(err, context.Canceled) {
|
|
l.setResult(nil)
|
|
fyne.Do(func() {
|
|
l.debugWindow.Hide()
|
|
l.app.Quit()
|
|
})
|
|
return
|
|
}
|
|
|
|
l.setCloseQuits(true)
|
|
l.setResult(err)
|
|
l.logError(err)
|
|
fyne.Do(func() {
|
|
l.btn.SetText("Retry")
|
|
l.btn.Enable()
|
|
l.btn.Show()
|
|
l.debugWindow.Show()
|
|
})
|
|
}
|
|
|
|
func (l *loader) onButtonAction() {
|
|
if l.ctx == nil {
|
|
return
|
|
}
|
|
go l.init(l.ctx)
|
|
}
|
|
|
|
func (l *loader) onWindowClose() {
|
|
if l.getCloseQuits() {
|
|
l.app.Quit()
|
|
return
|
|
}
|
|
|
|
l.debugWindow.Hide()
|
|
}
|
|
|
|
func (l *loader) logText(v string) {
|
|
if l.textGrid == nil {
|
|
return
|
|
}
|
|
fyne.Do(func() { l.textGrid.Append(v) })
|
|
}
|
|
|
|
func (l *loader) logError(err error) {
|
|
l.logText(fmt.Sprintf("ERROR: %s", err))
|
|
}
|
|
|
|
func (l *loader) setResult(err error) {
|
|
l.resultMu.Lock()
|
|
defer l.resultMu.Unlock()
|
|
l.result = err
|
|
}
|
|
|
|
func (l *loader) getResult() error {
|
|
l.resultMu.Lock()
|
|
defer l.resultMu.Unlock()
|
|
return l.result
|
|
}
|
|
|
|
func (l *loader) setCloseQuits(v bool) {
|
|
l.closeMu.Lock()
|
|
defer l.closeMu.Unlock()
|
|
l.closeQuits = v
|
|
}
|
|
|
|
func (l *loader) getCloseQuits() bool {
|
|
l.closeMu.Lock()
|
|
defer l.closeMu.Unlock()
|
|
return l.closeQuits
|
|
}
|
|
|
|
// Run starts the loader window, launches the standalone UI process, and returns
|
|
// the final launch result once the loader application exits.
|
|
func (l *loader) Run(ctx context.Context) error {
|
|
l.ctx = ctx
|
|
|
|
go l.init(ctx)
|
|
go func() {
|
|
<-ctx.Done()
|
|
fyne.Do(l.app.Quit)
|
|
}()
|
|
|
|
l.app.Run()
|
|
if errors.Is(ctx.Err(), context.Canceled) {
|
|
return nil
|
|
}
|
|
return l.getResult()
|
|
}
|