2020-01-02 01:50:15 +00:00
|
|
|
// Package updater checks for updates to Doodle.
|
|
|
|
package updater
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2021-07-11 19:10:59 +00:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2020-01-02 01:50:15 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/branding"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
// VersionInfo holds the version.json data for self-update check.
|
|
|
|
type VersionInfo struct {
|
|
|
|
LatestVersion string `json:"latestVersion"`
|
|
|
|
DownloadURL string `json:"downloadUrl"`
|
|
|
|
}
|
|
|
|
|
2021-07-11 19:10:59 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Last result of the update check, until forced to re-check.
|
|
|
|
var lastUpdate VersionInfo
|
|
|
|
|
|
|
|
// Check for new updates.
|
|
|
|
func Check() (VersionInfo, error) {
|
|
|
|
var result VersionInfo
|
|
|
|
|
|
|
|
// Return last cached check.
|
|
|
|
if lastUpdate.LatestVersion != "" {
|
|
|
|
return lastUpdate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
Timeout: 10 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debug("Checking for app updates")
|
|
|
|
|
|
|
|
resp, err := client.Get(branding.UpdateCheckJSON)
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("updater.Check: HTTP error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return result, fmt.Errorf("updater.Check: unexpected HTTP status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the JSON response.
|
|
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
|
|
|
err = json.Unmarshal(body, &result)
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("updater.Check: JSON parse error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
lastUpdate = result
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckNow forces a re-check of the update info.
|
|
|
|
func CheckNow() (VersionInfo, error) {
|
|
|
|
lastUpdate = VersionInfo{}
|
|
|
|
return Check()
|
|
|
|
}
|
2021-07-11 19:10:59 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
)
|