package loader import ( "context" "errors" "fmt" "galaxy/connector" mc "galaxy/model/client" "plugin" "time" "fyne.io/fyne/v2" ) type ClientInit func(connector.UIConnector, fyne.App, mc.Settings) (mc.Client, error) type loader struct { conn connector.Connector cli mc.Client } func NewLoader(app fyne.App, conn connector.Connector) (*loader, error) { app.Storage().List() settings := mc.Settings{} cli, err := loadClientPlugin(conn, app, settings, "./client.so", "NewClient") if err != nil { return nil, err } l := &loader{ conn: conn, cli: cli, } return l, nil } func (l *loader) Run(ctx context.Context) error { final := make(chan struct{}, 1) go l.backgroundLoop(ctx, final) if err := l.cli.Run(); err != nil { return err } final <- struct{}{} return nil } func (l *loader) backgroundLoop(ctx context.Context, final <-chan struct{}) { t := time.NewTicker(time.Second * 5) for { select { case <-ctx.Done(): l.cli.Shutdown() return case <-final: return case <-t.C: isGood := l.conn.CheckConnection() l.cli.OnConnection(isGood) } } } // 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(conn connector.UIConnector, app fyne.App, s mc.Settings, 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)(conn, app, s) }