refactor: loader package

This commit is contained in:
Ilia Denisov
2026-03-15 19:04:43 +02:00
parent 2134386625
commit 1ea03495f6
10 changed files with 5 additions and 88 deletions
+43
View File
@@ -0,0 +1,43 @@
package main
import (
"context"
"errors"
"fmt"
"galaxy/client/loader"
"galaxy/storage/fs"
"os"
"os/signal"
"fyne.io/fyne/v2/app"
)
func main() {
var err error
defer func() {
if err == nil {
if r := recover(); r != nil {
err = errors.Join(err, fmt.Errorf("app panics: %v", r))
}
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}()
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
app := app.NewWithID("galaxy-client")
s, err := fs.NewFS(app.Storage().RootURI().Path())
if err != nil {
return
}
l, err := loader.NewLoader(ctx, s, nil, app)
if err != nil {
return
}
err = l.Run(ctx)
}
@@ -2,7 +2,7 @@ package main
import (
"galaxy/client"
"galaxy/loader"
"galaxy/client/loader"
)
var Factory loader.ClientInit = client.NewClient
+3 -2
View File
@@ -4,7 +4,6 @@ go 1.26.0
require (
fyne.io/fyne/v2 v2.7.3
galaxy/loader v0.0.0
github.com/fogleman/gg v1.3.0
github.com/stretchr/testify v1.11.1
)
@@ -31,10 +30,11 @@ require (
github.com/hack-pad/safejs v0.1.1 // indirect
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rymdport/portal v0.4.2 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
@@ -43,5 +43,6 @@ require (
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+17
View File
@@ -0,0 +1,17 @@
package loader
import "galaxy/connector"
func (l *loader) newerVersion(version string) bool {
current := l.cli.Version()
return compareSemver(current, version) > 0
}
// downloadVersion fetches given version artifact, when newer to the current version,
// and stores at the App's local storage with a pre-defined name with semver suffix
func (l *loader) downloadVersion(v connector.VersionInfo) {
if !l.newerVersion(v.Version) {
return
}
l.conn.DownloadVersion(v.URL)
}
+112
View File
@@ -0,0 +1,112 @@
package loader
import (
"context"
"errors"
"fmt"
"galaxy/connector"
mc "galaxy/model/client"
"galaxy/storage"
"plugin"
"time"
"fyne.io/fyne/v2"
)
type ClientInit func(context.Context, storage.UIStorage, connector.UIConnector, fyne.App) (mc.Client, error)
type loader struct {
s storage.Storage
conn connector.Connector
cli mc.Client
}
const (
clientLibraryFile = "client"
)
var (
checkConnectionTimeout = time.Second * 5
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
}
l := &loader{
conn: conn,
cli: cli,
s: s,
}
return l, nil
}
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{}{}
return nil
}
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.cli.Shutdown()
return
case <-final:
return
case <-checkConnTimer.C:
isGood := l.conn.CheckConnection()
l.cli.OnConnection(isGood)
checkConnTimer.Reset(checkConnectionTimeout)
case <-checkVersionTimer.C:
versions, err := l.conn.CheckVersion()
if err != nil {
// propagate error to the UI
} else if latest, ok := latestVersion(versions); ok {
l.downloadVersion(latest)
}
checkVersionTimer.Reset(checkVersionTimeout)
}
}
}
// loadClientPlugin loads a Client implementation from a shared object (.so) 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) {
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)(ctx, s, conn, app)
}
+8
View File
@@ -0,0 +1,8 @@
package loader
func (l *loader) clientPluginVersionExists(version string) (bool, error) {
file := resolvePluginFile(version)
_ = file
// check file existence
return false, nil
}
+25
View File
@@ -0,0 +1,25 @@
package loader
import (
"galaxy/connector"
"runtime"
"slices"
)
func resolvePluginFile(version string) string {
return clientLibraryFile + "-" + version
}
func compareSemver(a, b string) int {
return 0
}
func latestVersion(versions []connector.VersionInfo) (connector.VersionInfo, bool) {
os := runtime.GOOS
versions = slices.DeleteFunc(versions, func(v connector.VersionInfo) bool { return v.OS != os })
if len(versions) == 0 {
return connector.VersionInfo{}, false
}
slices.SortFunc(versions, func(a, b connector.VersionInfo) int { return compareSemver(b.Version, a.Version) })
return versions[0], true
}