prepare loader ui

This commit is contained in:
Ilia Denisov
2026-03-15 20:40:25 +02:00
parent 1ea03495f6
commit fbcf4cef99
8 changed files with 191 additions and 57 deletions
+143 -30
View File
@@ -11,18 +11,25 @@ import (
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
type ClientInit func(context.Context, storage.UIStorage, connector.UIConnector, fyne.App) (mc.Client, error)
type ClientInit func(storage.UIStorage, connector.UIConnector, fyne.App) (mc.Client, error)
type loader struct {
s storage.Storage
conn connector.Connector
cli mc.Client
app fyne.App
storage storage.Storage
connector connector.Connector
client mc.Client
window fyne.Window
textGrid *widget.TextGrid
btn *widget.Button
}
const (
clientLibraryFile = "client"
pluginInitSymbol = "Factory"
libUIPluginFile = "libui"
)
var (
@@ -30,32 +37,136 @@ var (
checkVersionTimeout = time.Minute * 60
)
func NewLoader(ctx context.Context, s storage.Storage, conn connector.Connector, app fyne.App) (*loader, error) {
cli, err := loadClientPlugin(ctx, s, conn, app, "./client.so", "Factory")
if err != nil {
return nil, err
}
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{
conn: conn,
cli: cli,
s: s,
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 {
final := make(chan struct{}, 1)
if l.conn != nil {
go l.backgroundLoop(ctx, final)
defer func() { final <- struct{}{} }()
}
if err := l.cli.Run(); err != nil {
return err
}
final <- struct{}{}
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)
@@ -66,19 +177,21 @@ func (l *loader) backgroundLoop(ctx context.Context, final <-chan struct{}) {
for {
select {
case <-ctx.Done():
l.cli.Shutdown()
l.client.Shutdown()
return
case <-final:
return
case <-checkConnTimer.C:
isGood := l.conn.CheckConnection()
l.cli.OnConnection(isGood)
isGood := l.connector.CheckConnection()
l.client.OnConnection(isGood)
checkConnTimer.Reset(checkConnectionTimeout)
case <-checkVersionTimer.C:
versions, err := l.conn.CheckVersion()
versions, err := l.connector.CheckVersion()
if err != nil {
// propagate error to the UI
} else if latest, ok := latestVersion(versions); ok {
} else if latest, ok, err := latestVersion(versions); err != nil {
// propagate error to the UI
} else if ok {
l.downloadVersion(latest)
}
checkVersionTimer.Reset(checkVersionTimeout)
@@ -86,9 +199,9 @@ func (l *loader) backgroundLoop(ctx context.Context, final <-chan struct{}) {
}
}
// loadClientPlugin loads a Client implementation from a shared object (.so) file at the specified path.
// 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(ctx context.Context, s storage.UIStorage, conn connector.UIConnector, app fyne.App, path, name string) (mc.Client, error) {
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")
}
@@ -108,5 +221,5 @@ func loadClientPlugin(ctx context.Context, s storage.UIStorage, conn connector.U
return nil, fmt.Errorf("unexpected type %T; want %T", sym, initializerPtr)
}
return (*initializerPtr)(ctx, s, conn, app)
return (*initializerPtr)(s, conn, app)
}