Updater: Better SemVer version checks for updates

* Instead of a simple "cur. ver != latest ver" check, parse the Major,
  Minor and Patch components and do a detailed check.
* So a x.x.1 release could be made for a specific platform that had a
  bad build, and it won't mind when it sees the latest version is the
  older x.x.0 build that other platforms had working fine.
This commit is contained in:
Noah 2021-07-11 12:10:59 -07:00
parent fa15a8bcf5
commit 1105d9312a
6 changed files with 147 additions and 5 deletions

View File

@ -4,7 +4,7 @@ package branding
const (
AppName = "Sketchy Maze"
Summary = "A drawing-based maze game"
Version = "0.7.0"
Version = "0.7.1"
Website = "https://www.sketchymaze.com"
Copyright = "2021 Noah Petherbridge"

View File

@ -218,7 +218,7 @@ func (s *MainScene) checkUpdate() {
return
}
if info.LatestVersion != branding.Version {
if info.IsNewerVersionThan(branding.Version) {
s.updateInfo = info
s.updateButton.Show()
}

View File

@ -69,8 +69,6 @@ func (s *Supervisor) InstallScripts(level *level.Level) error {
// AddLevelScript adds a script to the supervisor with level hooks.
func (s *Supervisor) AddLevelScript(id string) error {
log.Debug("InstallScripts: load script from Actor %s", id)
if _, ok := s.scripts[id]; ok {
return fmt.Errorf("duplicate actor ID %s in level", id)
}

View File

@ -78,7 +78,6 @@ func (w *Canvas) InstallScripts() error {
}
// Call the main() function.
log.Debug("Calling Main() for %s", actor.ID())
if err := vm.Main(); err != nil {
log.Error("main() for actor %s errored: %s", actor.ID(), err)
}

View File

@ -6,6 +6,8 @@ import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strconv"
"time"
"git.kirsle.net/apps/doodle/pkg/branding"
@ -18,6 +20,42 @@ type VersionInfo struct {
DownloadURL string `json:"downloadUrl"`
}
// IsNewerVersionThan checks if the version.json houses a newer
// game version than the currently running game's version string.
func (i VersionInfo) IsNewerVersionThan(versionString string) bool {
// Parse the two semantic versions.
curSemver, err := ParseSemver(versionString)
if err != nil {
log.Error("VersionInfo.IsNewerVersionThan: didn't parse semver: %s", err)
return false
}
latestSemver, err := ParseSemver(i.LatestVersion)
if err != nil {
log.Error("VersionInfo.IsNewerVersionThan: didn't parse latest semver: %s", err)
return false
}
// Check if there's a newer version.
if latestSemver[Major] > curSemver[Major] {
// We're a major version behind, like 0.x.x < 1.x.x
return true
} else if latestSemver[Major] == curSemver[Major] {
// We're on the same major version, like 0.x.x
// Check the minor version.
if latestSemver[Minor] > curSemver[Minor] {
return true
} else if latestSemver[Minor] == curSemver[Minor] {
// Same minor version, check patch version.
if latestSemver[Patch] > curSemver[Patch] {
return true
}
}
}
return false
}
// Last result of the update check, until forced to re-check.
var lastUpdate VersionInfo
@ -61,3 +99,38 @@ func CheckNow() (VersionInfo, error) {
lastUpdate = VersionInfo{}
return Check()
}
// ParseSemver parses a Project: Doodle semantic version number into its
// three integer parts. Historical versions of the game looked like
// either "0.1.0-alpha" or "0.7.0" and the given version string is
// expected to match that pattern.
func ParseSemver(versionString string) ([3]int, error) {
var numbers [3]int
result := semverRegexp.FindStringSubmatch(versionString)
if result == nil {
return numbers, fmt.Errorf("%s: didn't parse as a valid semver string", versionString)
}
// string to int helper
var atoi = func(v string) int {
a, _ := strconv.Atoi(v)
return a
}
numbers = [3]int{
atoi(result[1]),
atoi(result[2]),
atoi(result[3]),
}
return numbers, nil
}
var semverRegexp = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+).*`)
// Some semantic version indexing constants, to parse the result of
// ParseSemver with more semantic code.
const (
Major int = iota
Minor
Patch
)

View File

@ -0,0 +1,72 @@
package updater_test
import (
"testing"
"git.kirsle.net/apps/doodle/pkg/updater"
)
// Test the semver logic.
func TestParseSemver(t *testing.T) {
var tests = []struct {
A string // version strings
B string
Expect bool // expect B is newer version than A
}{
{
"0.1.0-alpha",
"0.1.0-alpha",
false,
},
{
"0.1.0-alpha",
"0.2.0-alpha",
true,
},
{
"0.2.0-alpha",
"0.2.0-alpha",
false,
},
{
"0.2.0-alpha",
"0.1.0-alpha",
false,
},
{
"0.2.0-alpha",
"0.2.1-alpha",
true,
},
{
"0.1.0-alpha",
"0.2.1-alpha",
true,
},
{
"0.2.1-alpha",
"0.3.0",
true,
},
{
"0.3.0",
"0.4.1",
true,
},
{
"0.1",
"0.2.0",
false,
},
}
for i, test := range tests {
result := updater.VersionInfo{
LatestVersion: test.B,
}.IsNewerVersionThan(test.A)
if result != test.Expect {
t.Errorf("Test %d: %s <> %s: expected %+v but got %+v",
i, test.A, test.B, test.Expect, result,
)
}
}
}