Files
galaxy-game/client/loader/loader.go
T
2026-03-15 20:40:25 +02:00

226 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package loader
import (
"context"
"errors"
"fmt"
"galaxy/connector"
mc "galaxy/model/client"
"galaxy/storage"
"plugin"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
type ClientInit func(storage.UIStorage, connector.UIConnector, fyne.App) (mc.Client, error)
type loader struct {
app fyne.App
storage storage.Storage
connector connector.Connector
client mc.Client
window fyne.Window
textGrid *widget.TextGrid
btn *widget.Button
}
const (
pluginInitSymbol = "Factory"
libUIPluginFile = "libui"
)
var (
checkConnectionTimeout = time.Second * 5
checkVersionTimeout = time.Minute * 60
)
func NewLoader(s storage.Storage, conn connector.Connector, app fyne.App) (*loader, error) {
// cli, err := loadClientPlugin(s, conn, app, "./client.so", pluginInitSymbol)
// if err != nil {
// return nil, err
// }
l := &loader{
app: app,
connector: conn,
// client: cli,
storage: s,
textGrid: widget.NewTextGrid(),
window: app.NewWindow("Loader"),
}
l.btn = widget.NewButton("OK", l.onButtonAction)
l.btn.Disable()
content := container.NewStack(
l.textGrid,
container.NewHBox(
container.NewCenter(
l.btn,
),
),
)
l.window.SetContent(content)
return l, nil
}
/*
# Порядок инициализации
- При ошибках Connector: не фатальное состояние, отображать состояние недоступности сети, предлагать повторить попытку.
- При ошибках Storage: фатальное, отобразить рекомендацию переустановить клиента, выход после confirm от пользователя.
1. Проверяем полный путь libUIPluginFile с помощью Storage.FileExists;
2. Если libUIPluginFile не существует в Storage:
2.1. запрашиваем Connector.CheckVersion,
2.2. с помощью хэлпера latestVersion(...) определяем самую свежую версию;
2.3. загружаем полученную версию через Connector.DownloadVersion,
2.4. сохраняем libUIPluginFile в Storage.WriteFile.
3. Загружаем libUIPluginFile full path в loadClientPlugin;
*/
func (l *loader) initUIPlugin() (mc.Client, error) {
l.appendText(fmt.Sprintf("checking plugin at path: %s", libUIPluginFile))
exists, err := l.storage.FileExists(libUIPluginFile)
if err != nil {
l.appendFatalError(err)
return nil, err
}
if !exists {
l.appendText(fmt.Sprintf("plugin not found at %s, fetching available versions", libUIPluginFile))
v, err := l.connector.CheckVersion()
if err != nil {
l.appendFatalError(err)
return nil, err
}
l.appendText(fmt.Sprintf("received %d versions", len(v)))
latest, ok, err := latestVersion(v)
if err != nil {
l.appendFatalError(err)
return nil, err
}
if !ok {
l.appendFatalError(errors.New("no latest version available from response"))
return nil, err
}
_ = latest
} else {
l.appendText(fmt.Sprintf("plugin found at %s", libUIPluginFile))
}
// implement this
return nil, nil
}
func (l *loader) startLoading() {
l.appendText("Loading...")
var err error
l.client, err = l.initUIPlugin()
if err != nil {
l.window.Show()
}
}
func (l *loader) onButtonAction() {
l.app.Quit()
}
func (l *loader) Run(ctx context.Context) error {
go l.startLoading()
l.app.Run()
// l.window.Show()
// l.startLoading()
// select {
// case <-ctx.Done():
// return nil
// case <-l.await:
// return nil
// }
// final := make(chan struct{}, 1)
// if l.connector != nil {
// go l.backgroundLoop(ctx, final)
// defer func() { final <- struct{}{} }()
// }
// if err := l.client.Run(); err != nil {
// return err
// }
// final <- struct{}{}
return nil
}
func (l *loader) appendText(v string) {
fmt.Println(v)
fyne.Do(func() { l.textGrid.Append(v) })
}
func (l *loader) appendError(err error) {
l.appendText(fmt.Sprintf("Error: %s", err))
}
func (l *loader) appendFatalError(err error) {
l.appendError(err)
l.btn.Enable()
}
func (l *loader) backgroundLoop(ctx context.Context, final <-chan struct{}) {
checkConnTimer := time.NewTimer(checkConnectionTimeout)
checkVersionTimer := time.NewTimer(checkVersionTimeout)
defer func() {
checkConnTimer.Stop()
checkVersionTimer.Stop()
}()
for {
select {
case <-ctx.Done():
l.client.Shutdown()
return
case <-final:
return
case <-checkConnTimer.C:
isGood := l.connector.CheckConnection()
l.client.OnConnection(isGood)
checkConnTimer.Reset(checkConnectionTimeout)
case <-checkVersionTimer.C:
versions, err := l.connector.CheckVersion()
if err != nil {
// propagate error to the UI
} else if latest, ok, err := latestVersion(versions); err != nil {
// propagate error to the UI
} else if ok {
l.downloadVersion(latest)
}
checkVersionTimer.Reset(checkVersionTimeout)
}
}
}
// loadClientPlugin loads a Client implementation from a shared plugin file at the specified path.
// It calls the constructor function by name, passing the necessary dependencies, and returns the initialized Client.
func loadClientPlugin(s storage.UIStorage, conn connector.UIConnector, app fyne.App, path, name string) (mc.Client, error) {
if path == "" {
return nil, errors.New("no plugin path given")
}
plug, err := plugin.Open(path)
if err != nil {
return nil, fmt.Errorf("open plugin %q: %w", path, err)
}
sym, err := plug.Lookup(name)
if err != nil {
return nil, fmt.Errorf("lookup symbol %q: %w", name, err)
}
initializerPtr, ok := sym.(*ClientInit)
if !ok {
return nil, fmt.Errorf("unexpected type %T; want %T", sym, initializerPtr)
}
return (*initializerPtr)(s, conn, app)
}