diff --git a/client/client.go b/client/client.go index 71d28fc..4885029 100644 --- a/client/client.go +++ b/client/client.go @@ -1,7 +1,6 @@ package client import ( - "context" "image" "sync" @@ -70,7 +69,7 @@ type client struct { hits []world.Hit } -func NewClient(ctx context.Context, s storage.UIStorage, conn connector.UIConnector, app fyne.App) (mc.Client, error) { +func NewClient(s storage.UIStorage, conn connector.UIConnector, app fyne.App) (mc.Client, error) { e := &client{ s: s, conn: conn, @@ -85,7 +84,7 @@ func NewClient(ctx context.Context, s storage.UIStorage, conn connector.UIConnec hits: make([]world.Hit, 5), } - e.loadReportFunc = func(t uint) { e.loadReport(ctx, t) } + e.loadReportFunc = e.loadReport e.drawer = &world.GGDrawer{DC: nil} @@ -106,7 +105,7 @@ func NewClient(ctx context.Context, s storage.UIStorage, conn connector.UIConnec return e, nil } -func (e *client) loadReport(ctx context.Context, t uint) { +func (e *client) loadReport(t uint) { e.conn.FetchReport("GAME_ID", t, func(r report.Report, err error) { if err != nil { e.handlerError(err) diff --git a/client/cmd/loader/main.go b/client/cmd/loader/main.go index 1d7f532..4710306 100644 --- a/client/cmd/loader/main.go +++ b/client/cmd/loader/main.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "galaxy/client/loader" + "galaxy/connector/http" "galaxy/storage/fs" "os" "os/signal" @@ -29,12 +30,16 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - app := app.NewWithID("galaxy-client") + app := app.NewWithID("GalaxyPlus") s, err := fs.NewFS(app.Storage().RootURI().Path()) if err != nil { return } - l, err := loader.NewLoader(ctx, s, nil, app) + c, err := http.NewHttpConnector(ctx, "http://127.0.0.1:8080") + if err != nil { + return + } + l, err := loader.NewLoader(s, c, app) if err != nil { return } diff --git a/client/cmd/ui/main.go b/client/cmd/ui/main.go index 8b76bf3..43d9dbb 100644 --- a/client/cmd/ui/main.go +++ b/client/cmd/ui/main.go @@ -1,12 +1,10 @@ package main import ( - "context" "errors" "fmt" "galaxy/client" "os" - "os/signal" "fyne.io/fyne/v2/app" ) @@ -25,11 +23,7 @@ func main() { } }() app := app.New() - - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - - c, err := client.NewClient(ctx, nil, nil, app) + c, err := client.NewClient(nil, nil, app) if err != nil { return } diff --git a/client/loader/download.go b/client/loader/download.go index 019276a..4541446 100644 --- a/client/loader/download.go +++ b/client/loader/download.go @@ -1,10 +1,12 @@ package loader -import "galaxy/connector" +import ( + "galaxy/connector" + "galaxy/util" +) func (l *loader) newerVersion(version string) bool { - current := l.cli.Version() - return compareSemver(current, version) > 0 + return util.CompareSemver(util.MustParseSemver(l.client.Version()), util.MustParseSemver(version)) > 0 } // downloadVersion fetches given version artifact, when newer to the current version, @@ -13,5 +15,5 @@ func (l *loader) downloadVersion(v connector.VersionInfo) { if !l.newerVersion(v.Version) { return } - l.conn.DownloadVersion(v.URL) + l.connector.DownloadVersion(v.URL) } diff --git a/client/loader/loader.go b/client/loader/loader.go index 31db0c4..87b8f0e 100644 --- a/client/loader/loader.go +++ b/client/loader/loader.go @@ -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) } diff --git a/client/loader/util.go b/client/loader/util.go index cd76bbe..7cfc8b0 100644 --- a/client/loader/util.go +++ b/client/loader/util.go @@ -1,25 +1,39 @@ package loader import ( + "fmt" "galaxy/connector" + "galaxy/util" "runtime" "slices" ) func resolvePluginFile(version string) string { - return clientLibraryFile + "-" + version + return libUIPluginFile + "-" + version } -func compareSemver(a, b string) int { - return 0 -} - -func latestVersion(versions []connector.VersionInfo) (connector.VersionInfo, bool) { +// latestVersion should return VersionInfo with the latest Version for the current OD +func latestVersion(versions []connector.VersionInfo) (connector.VersionInfo, bool, error) { os := runtime.GOOS versions = slices.DeleteFunc(versions, func(v connector.VersionInfo) bool { return v.OS != os }) if len(versions) == 0 { - return connector.VersionInfo{}, false + return connector.VersionInfo{}, false, nil } - slices.SortFunc(versions, func(a, b connector.VersionInfo) int { return compareSemver(b.Version, a.Version) }) - return versions[0], true + type v struct { + vi *connector.VersionInfo + sv *util.SemVer + } + semvers := make([]*v, len(versions)) + for i := range versions { + sv, err := util.ParseSemver(versions[i].Version) + if err != nil { + return connector.VersionInfo{}, false, fmt.Errorf("latest version: %w", err) + } + semvers[i] = &v{ + vi: &versions[i], + sv: &sv, + } + } + slices.SortFunc(semvers, func(a, b *v) int { return util.CompareSemver(*b.sv, *a.sv) }) + return *semvers[0].vi, true, nil } diff --git a/client/model/state.go b/client/model/state.go deleted file mode 100644 index 8b53790..0000000 --- a/client/model/state.go +++ /dev/null @@ -1 +0,0 @@ -package model diff --git a/pkg/util/semver.go b/pkg/util/semver.go index 67dcbbe..b3fb615 100644 --- a/pkg/util/semver.go +++ b/pkg/util/semver.go @@ -98,6 +98,14 @@ func ParseSemver(input string) (SemVer, error) { return version, nil } +func MustParseSemver(input string) SemVer { + if v, err := ParseSemver(input); err != nil { + panic(err) + } else { + return v + } +} + // CompareSemver compares two semantic versions and returns: // // +1 if x is less than y,