Skip to content

Commit

Permalink
feat(updater): add self-update from github releases
Browse files Browse the repository at this point in the history
  • Loading branch information
mJehanno committed Feb 24, 2024
1 parent 5353daf commit 94af43c
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 90 deletions.
34 changes: 5 additions & 29 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,46 +18,22 @@ jobs:
fail-fast: false
matrix:
build:
- name: 'mult-game'
- name: 'mult-game_linux-amd64'
platform: 'linux/amd64'
arch: 'linux-amd64'
os: 'ubuntu-latest'
- name: 'mult-game'
- name: 'mult-game_windows-amd64'
platform: 'windows/amd64'
arch: 'windows-amd64'
os: 'windows-latest'
- name: 'mult-game'
platform: 'darwin/universal'
os: 'macos-latest'
arch: 'darwin-universal'

runs-on: ${{ matrix.build.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive

- name: Build wails
- name: Build & Deploy
uses: dAppServer/[email protected]
id: build
with:
build-name: ${{ matrix.build.name }}_${{ matrix.build.arch }}
build-name: ${{ matrix.build.name }}
build-platform: ${{ matrix.build.platform }}
package: false
go-version: '1.21'
# - name: Build Windows App + Installer
# run: wails build --platform windows/amd64 -webview2 download -nsis -o 'mult-game-installer'
# shell: bash
- uses: actions/upload-artifact@v3
with:
name: Wails Build ${{runner.os}} mult-game
path: |
*/bin/
*\bin\*
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
*/bin/*
go-version: 1.21
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
# README
# Mult-game

## About

This is the official Wails Vue-TS template.
Mult-game is a little game made to help memorizing multiplication table.

You can configure the project by editing `wails.json`. More information about the project settings can be found
here: https://wails.io/docs/reference/project-config
It's pretty simple : once you started the game, it will ask you to solve a multiplication in less than 20 sec.
- If you succeed, it will increase your current streak. Every five successful answer, the max timer duration will decrease by 4 second to a limit of 4 second (to ensure you remembered the results).
- If you fail, it's game over. You'll be able to register your score (using a 3 characters long nickname) and start over !.

## Live Development
The game is supposed to be able to update itself on launch.

To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.
More features are to come :

## Building

To build a redistributable, production mode package, use `wails build`.
- [ ] live score display
- [ ] new difficulty mode (easier than the existing one)
- [ ] might add some other mod later
1 change: 0 additions & 1 deletion backend/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func GetDbConnection(logger *logger.Logger) *Db {
}

func (db *Db) createTable() {
db.logger.DebugLogger.Debug("creating table")
_, err := db.Conn.Exec(schema)
if err != nil {
db.logger.ErrLogger.WithError(err).Error("failed to create table rank in database")
Expand Down
25 changes: 15 additions & 10 deletions backend/score/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

type ScoreService struct {
logger *logger.Logger
data *db.Db
Logger *logger.Logger
Data *db.Db
}

type ScoreDB struct {
Expand All @@ -39,16 +39,21 @@ func (s ScoreDB) Convert(dest *Score) {
}

type Score struct {
fx.In
Username string `db:"username" json:"username"`
Score int `db:"score" json:"score"`
Date time.Time `db:"created_at" goqu:"skipinsert" json:"created_at"`
}

func NewScoreService(logger *logger.Logger, conn *db.Db) *ScoreService {
type ScoreParams struct {
fx.In
Logger *logger.Logger
Conn *db.Db
}

func NewScoreService(p ScoreParams) *ScoreService {
return &ScoreService{
logger: logger,
data: conn,
Logger: p.Logger,
Data: p.Conn,
}
}

Expand All @@ -57,15 +62,15 @@ func (s *ScoreService) GetScore() []Score {
resDB []*ScoreDB
res []Score
)
query := s.data.Conn.Select(
query := s.Data.Conn.Select(
goqu.C("username"),
goqu.C("score"),
goqu.C("created_at"),
).From("rank").Order(goqu.C("score").Desc()).Limit(10)

err := query.ScanStructsContext(context.Background(), &resDB)
if err != nil {
s.logger.ErrLogger.WithError(err).Error("failed to retrieve ranks from database")
s.Logger.ErrLogger.WithError(err).Error("failed to retrieve ranks from database")
return nil
}

Expand All @@ -80,10 +85,10 @@ func (s *ScoreService) GetScore() []Score {

func (s ScoreService) AddScore(sc Score) error {

query := s.data.Conn.Insert("rank").Prepared(true).Rows(sc).Executor()
query := s.Data.Conn.Insert("rank").Prepared(true).Rows(sc).Executor()

if _, err := query.Exec(); err != nil {
s.logger.ErrLogger.WithError(err).Error("failed to add rank in database")
s.Logger.ErrLogger.WithError(err).Error("failed to add rank in database")
return err
} else {
return nil
Expand Down
194 changes: 194 additions & 0 deletions backend/update/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package update

import (
"context"
"fmt"
"io"
"mult-game/backend/logger"
"mult-game/backend/version"
"net/http"
"os"
"os/exec"
"path"
"runtime"
"strings"

"github.com/blang/semver"
"github.com/google/go-github/v59/github"
"go.uber.org/fx"
"golang.org/x/exp/slices"
)

const (
owner = "mJehanno"
repo = "op-game"
)

type ErrorHandler struct {
log *logger.Logger
}

func NewErrorHandler(log *logger.Logger) *ErrorHandler {
return &ErrorHandler{
log: log,
}
}

func (eh *ErrorHandler) handleErr(err error, message string) {
if err != nil {
eh.log.ErrLogger.WithError(err).Error(message)
}
}

type Updater struct {
log *logger.Logger
eh *ErrorHandler
vm *version.VersionManager
client *github.Client
latest semver.Version
rel *github.RepositoryRelease
platform string
dlPath string
}

type UpdaterParams struct {
fx.In

Log *logger.Logger
Eh *ErrorHandler
Vm *version.VersionManager
Client *github.Client
}

func NewUpdater(p UpdaterParams) *Updater {
platf := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
return &Updater{
log: p.Log,
eh: p.Eh,
vm: p.Vm,
client: p.Client,
platform: platf,
}
}

func (u *Updater) isCurrentLatest(ctx context.Context) bool {
rel, _, err := u.client.Repositories.GetLatestRelease(ctx, owner, repo)
if err != nil {
u.eh.handleErr(err, "failed to retrieve latest release")
return true
}
u.rel = rel

latest := semver.MustParse(strings.ReplaceAll(rel.GetTagName(), "v", ""))
u.latest = latest
return latest.EQ(semver.MustParse(u.vm.GetCurrentVersion()))
}

func (u *Updater) getAsset() (*github.ReleaseAsset, error) {

index := slices.IndexFunc(u.rel.Assets, func(ra *github.ReleaseAsset) bool {
return strings.Contains(*ra.Name, u.platform)
})

if index == -1 {
err := fmt.Errorf("asszt not found")
u.log.ErrLogger.WithError(err).Error("failed to retrieve corresponding release asset")
return nil, err
}

return u.rel.Assets[index], nil
}

func (u *Updater) downloadAsset(ctx context.Context, asset github.ReleaseAsset) error {
reader, redirect, err := u.client.Repositories.DownloadReleaseAsset(ctx, owner, repo, asset.GetID(), http.DefaultClient)
if err != nil {
err = fmt.Errorf("failed to download release asset -> %w", err)
return err
}

if redirect != "" {
return fmt.Errorf("need to learn how to use the redirect url")
}

u.dlPath = path.Join(os.TempDir(), *asset.Name)

f, err := os.Create(u.dlPath)
if err != nil {
err = fmt.Errorf("failed to create temp downloaded release asset -> %w", err)
return err
}

defer func() {
f.Close()
reader.Close()
}()

_, err = io.Copy(f, reader)
if err != nil {
err = fmt.Errorf("failed to write downloaded release asset -> %w", err)
return err
}
return nil
}

func (u *Updater) installNewRelease() error {
exePath, err := os.Executable()
if err != nil {
u.log.ErrLogger.WithError(err).Error("failed to retrieve current process executable")
return err
}

err = os.Rename(exePath, fmt.Sprintf("%s-old", exePath))
if err != nil {
u.log.ErrLogger.WithError(err).Error("failed to rename old binary")
return err
}

if strings.Contains(u.platform, "linux") {
os.Rename(u.dlPath, exePath)
os.Chmod(exePath, 0775)
err := exec.Command(exePath).Run()
if err != nil {
u.log.ErrLogger.WithError(err).Error("failed to launch new binary")
// rollingback
err = os.Remove(exePath)
if err != nil {
u.log.ErrLogger.WithError(err).Error("failed to remove new binary")
}
err = os.Rename(fmt.Sprintf("%s-old", exePath), exePath)
if err != nil {
u.log.ErrLogger.WithError(err).Error("failed to rename old binary")
}
return err
}
}

return nil
}

func (u *Updater) DoSelfUpdate(ctx context.Context) {
if u.isCurrentLatest(ctx) {
u.log.DebugLogger.Debug("current version is the latest")
return
}
u.log.DebugLogger.Debugf("latest version is %s, current is %s", u.latest, u.vm.GetCurrentVersion())

asset, err := u.getAsset()
u.eh.handleErr(err, "failed to find asset")
if err != nil {
return
}

err = u.downloadAsset(ctx, *asset)
u.eh.handleErr(err, "failed to download asset")
if err != nil {
return
}

err = u.installNewRelease()
u.eh.handleErr(err, "failed to install new release")
if err == nil {
os.Exit(0)
}

}
Binary file modified build/windows/icon.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion frontend/package.json.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
64960eb47bdef8bcd540e66fad35bb73
3a91b86ead97a4984943fff36a4cf9ac
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module mult-game
go 1.21

require (
github.com/blang/semver v3.5.1+incompatible
github.com/doug-martin/goqu/v9 v9.19.0
github.com/google/go-github/v59 v59.0.1-0.20240217151021-73422173c633
github.com/sirupsen/logrus v1.9.3
github.com/wailsapp/wails/v2 v2.8.0
github.com/wailsapp/wails/v2 v2.7.1
go.uber.org/fx v1.20.1
modernc.org/sqlite v1.29.1
)
Expand All @@ -15,6 +17,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
Expand Down Expand Up @@ -42,7 +45,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
Loading

0 comments on commit 94af43c

Please sign in to comment.