Files
galaxy-game/client/loader/loader.go
T
2026-03-15 21:14:09 +02:00

247 lines
6.5 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
fatalError bool
}
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) {
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 client plugin at %s", libUIPluginFile))
// exists, err := l.storage.FileExists(libUIPluginFile)
// if err != nil {
// l.appendFatalError(err)
// return nil, err
// }
// if !exists {
// l.appendText("Client plugin not found, checking available versions")
// v, err := l.connector.CheckVersion()
// if err != nil {
// l.appendError(err)
// return nil, err
// }
// l.appendText(fmt.Sprintf("Received %d versions", len(v)))
// latest, ok, err := latestVersion(v)
// if err != nil {
// l.appendError(err)
// return nil, err
// }
// if !ok {
// l.appendError(errors.New("Server did not responded with a suitable client version"))
// return nil, err
// }
// l.appendText(fmt.Sprintf("Downloading version %s", latest.Version))
// data, err := l.connector.DownloadVersion(latest.URL)
// if err != nil {
// l.appendError(fmt.Errorf("Version %s download error: %w", latest.Version, err))
// return nil, err
// }
// err = l.storage.WriteFile(libUIPluginFile, data)
// if err != nil {
// l.appendFatalError(fmt.Errorf("Write plugin file error: %w", err))
// return nil, err
// }
// }
// l.appendText(fmt.Sprintf("Loading client plugin from %s", libUIPluginFile))
// cli, err := loadClientPlugin(s, conn, app, "./client.so", pluginInitSymbol)
// if err != nil {
// return nil, err
// }
return nil, nil
}
func (l *loader) startLoading() {
l.fatalError = false
fyne.Do(func() {
l.textGrid.SetText("")
l.btn.Hide()
l.btn.Disable()
})
l.appendText("Loading...")
var err error
l.client, err = l.initUIPlugin()
if err != nil {
fyne.Do(func() {
if l.fatalError {
l.btn.SetText("Quit")
l.appendText("Please re-install application.")
} else {
l.btn.SetText("Retry")
}
l.btn.Enable()
l.btn.Show()
l.window.Show()
})
}
}
func (l *loader) onButtonAction() {
if l.fatalError {
l.app.Quit()
} else {
go l.startLoading()
}
}
func (l *loader) Run(ctx context.Context) error {
go l.startLoading()
l.app.Run()
// 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("❌ %s", err))
}
func (l *loader) appendFatalError(err error) {
l.appendError(err)
l.fatalError = true
}
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)
}