226 lines
5.7 KiB
Go
226 lines
5.7 KiB
Go
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)
|
||
}
|