From 901a7e59ecd1437eb3bcaa71bf7ba4c466fc55d7 Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Wed, 8 Feb 2023 06:02:23 +0200 Subject: [PATCH] Add additional latest version info (#3357) --- graphql/documents/queries/misc.graphql | 2 + graphql/schema/schema.graphql | 2 +- graphql/schema/types/version.graphql | 4 +- internal/api/check_version.go | 190 ++++++++---------- internal/api/resolver.go | 25 ++- internal/api/server.go | 39 +++- .../Settings/SettingsAboutPanel.tsx | 110 ++++++---- ui/v2.5/src/locales/en-GB.json | 1 + 8 files changed, 206 insertions(+), 167 deletions(-) diff --git a/graphql/documents/queries/misc.graphql b/graphql/documents/queries/misc.graphql index b8b4871d1..decb1c652 100644 --- a/graphql/documents/queries/misc.graphql +++ b/graphql/documents/queries/misc.graphql @@ -67,7 +67,9 @@ query Version { query LatestVersion { latestversion { + version shorthash + release_date url } } diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 3ac138ac8..3bdcba5aa 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -155,7 +155,7 @@ type Query { version: Version! # LatestVersion - latestversion: ShortVersion! + latestversion: LatestVersion! } type Mutation { diff --git a/graphql/schema/types/version.graphql b/graphql/schema/types/version.graphql index 305c431cf..4385430ca 100644 --- a/graphql/schema/types/version.graphql +++ b/graphql/schema/types/version.graphql @@ -4,7 +4,9 @@ type Version { build_time: String! } -type ShortVersion { +type LatestVersion { + version: String! shorthash: String! + release_date: String! url: String! } diff --git a/internal/api/check_version.go b/internal/api/check_version.go index 90e7e5b88..994e91567 100644 --- a/internal/api/check_version.go +++ b/internal/api/check_version.go @@ -20,12 +20,7 @@ import ( const apiReleases string = "https://api.github.com/repos/stashapp/stash/releases" const apiTags string = "https://api.github.com/repos/stashapp/stash/tags" const apiAcceptHeader string = "application/vnd.github.v3+json" -const developmentTag string = "latest_develop" -const defaultSHLength int = 7 // default length of SHA short hash returned by - -// ErrNoVersion indicates that no version information has been embedded in the -// stash binary -var ErrNoVersion = errors.New("no stash version") +const defaultSHLength int = 8 // default length of SHA short hash returned by var stashReleases = func() map[string]string { return map[string]string{ @@ -108,6 +103,14 @@ type githubTagResponse struct { Node_id string } +type LatestRelease struct { + Version string + Hash string + ShortHash string + Date string + Url string +} + func makeGithubRequest(ctx context.Context, url string, output interface{}) error { transport := &http.Transport{Proxy: http.ProxyFromEnvironment} @@ -148,14 +151,16 @@ func makeGithubRequest(ctx context.Context, url string, output interface{}) erro return nil } -// GetLatestVersion gets latest version (git commit hash) from github API +// GetLatestRelease gets latest release information from github API // If running a build from the "master" branch, then the latest full release // is used, otherwise it uses the release that is tagged with "latest_develop" // which is the latest pre-release build. -func GetLatestVersion(ctx context.Context, shortHash bool) (latestVersion string, latestRelease string, err error) { +func GetLatestRelease(ctx context.Context) (*LatestRelease, error) { + arch := runtime.GOARCH - arch := runtime.GOARCH // https://en.wikipedia.org/wiki/Comparison_of_ARM_cores - isARMv7 := cpu.ARM.HasNEON || cpu.ARM.HasVFPv3 || cpu.ARM.HasVFPv3D16 || cpu.ARM.HasVFPv4 // armv6 doesn't support any of these features + // https://en.wikipedia.org/wiki/Comparison_of_ARM_cores + // armv6 doesn't support any of these features + isARMv7 := cpu.ARM.HasNEON || cpu.ARM.HasVFPv3 || cpu.ARM.HasVFPv3D16 || cpu.ARM.HasVFPv4 if arch == "arm" && isARMv7 { arch = "armv7" } @@ -163,125 +168,100 @@ func GetLatestVersion(ctx context.Context, shortHash bool) (latestVersion string platform := fmt.Sprintf("%s/%s", runtime.GOOS, arch) wantedRelease := stashReleases()[platform] - version, _, _ := GetVersion() - if version == "" { - return "", "", ErrNoVersion - } - - // if the version is suffixed with -x-xxxx, then we are running a development build - usePreRelease := false - re := regexp.MustCompile(`-\d+-g\w+$`) - if re.MatchString(version) { - usePreRelease = true - } - - url := apiReleases - if !usePreRelease { - // just get the latest full release - url += "/latest" + var release githubReleasesResponse + if IsDevelop() { + // get the latest release, prerelease or not + releases := []githubReleasesResponse{} + err := makeGithubRequest(ctx, apiReleases+"?per_page=1", &releases) + if err != nil { + return nil, err + } + release = releases[0] } else { - // get the release tagged with the development tag - url += "/tags/" + developmentTag + // just get the latest full release + err := makeGithubRequest(ctx, apiReleases+"/latest", &release) + if err != nil { + return nil, err + } } - release := githubReleasesResponse{} - err = makeGithubRequest(ctx, url, &release) + version := release.Name + if release.Prerelease { + // find version in prerelease name + re := regexp.MustCompile(`v[\w-\.]+-\d+-g[0-9a-f]+`) + if match := re.FindString(version); match != "" { + version = match + } + } + latestHash, err := getReleaseHash(ctx, release.Tag_name) if err != nil { - return "", "", err + return nil, err } - if release.Prerelease == usePreRelease { - latestVersion = getReleaseHash(ctx, release, shortHash, usePreRelease) + var releaseDate string + if publishedAt, err := time.Parse(time.RFC3339, release.Published_at); err == nil { + releaseDate = publishedAt.Format("2006-01-02") + } - if wantedRelease != "" { - for _, asset := range release.Assets { - if asset.Name == wantedRelease { - latestRelease = asset.Browser_download_url - break - } + var releaseUrl string + if wantedRelease != "" { + for _, asset := range release.Assets { + if asset.Name == wantedRelease { + releaseUrl = asset.Browser_download_url + break } } } - if latestVersion == "" { - return "", "", fmt.Errorf("no version found for \"%s\"", version) + _, githash, _ := GetVersion() + shLength := len(githash) + if shLength == 0 { + shLength = defaultSHLength } - return latestVersion, latestRelease, nil + + return &LatestRelease{ + Version: version, + Hash: latestHash, + ShortHash: latestHash[:shLength], + Date: releaseDate, + Url: releaseUrl, + }, nil } -func getReleaseHash(ctx context.Context, release githubReleasesResponse, shortHash bool, usePreRelease bool) string { - shaLength := len(release.Target_commitish) - // the /latest API call doesn't return the hash in target_commitish - // also add sanity check in case Target_commitish is not 40 characters - if !usePreRelease || shaLength != 40 { - return getShaFromTags(ctx, shortHash, release.Tag_name) - } - - if shortHash { - last := defaultSHLength // default length of git short hash - _, gitShort, _ := GetVersion() // retrieve it to check actual length - if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer - last = len(gitShort) - } - return release.Target_commitish[0:last] - } - - return release.Target_commitish -} - -func printLatestVersion(ctx context.Context) { - _, githash, _ = GetVersion() - latest, _, err := GetLatestVersion(ctx, true) - if err != nil { - logger.Errorf("Couldn't find latest version: %s", err) - } else { - if githash == latest { - logger.Infof("Version: (%s) is already the latest released.", latest) - } else { - logger.Infof("New version: (%s) available.", latest) - } - } -} - -// get sha from the github api tags endpoint -// returns the sha1 hash/shorthash or "" if something's wrong -func getShaFromTags(ctx context.Context, shortHash bool, name string) string { +func getReleaseHash(ctx context.Context, tagName string) (string, error) { url := apiTags tags := []githubTagResponse{} err := makeGithubRequest(ctx, url, &tags) - if err != nil { - // If the context is canceled, we don't want to log this as an error - // in the path. The function here just gives up and returns "" if - // something goes wrong. Hence, log the error at the info-level so - // it's still present, but don't treat this as an error. - if errors.Is(err, context.Canceled) { - logger.Infof("aborting sha request due to context cancellation") - } else { - logger.Errorf("Github Tags Api: %v", err) - } - return "" + return "", err } - _, gitShort, _ := GetVersion() // retrieve short hash to check actual length for _, tag := range tags { - if tag.Name == name { - shaLength := len(tag.Commit.Sha) - if shaLength != 40 { - return "" + if tag.Name == tagName { + if len(tag.Commit.Sha) != 40 { + return "", errors.New("invalid Github API response") } - if shortHash { - last := defaultSHLength // default length of git short hash - if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer - last = len(gitShort) - } - return tag.Commit.Sha[0:last] - } - - return tag.Commit.Sha + return tag.Commit.Sha, nil } } - return "" + return "", errors.New("invalid Github API response") +} + +func printLatestVersion(ctx context.Context) { + latestRelease, err := GetLatestRelease(ctx) + if err != nil { + logger.Errorf("Couldn't retrieve latest version: %v", err) + } else { + _, githash, _ = GetVersion() + switch { + case githash == "": + logger.Infof("Latest version: %s (%s)", latestRelease.Version, latestRelease.ShortHash) + case githash == latestRelease.ShortHash: + logger.Infof("Version %s (%s) is already the latest released", latestRelease.Version, latestRelease.ShortHash) + default: + logger.Infof("New version available: %s (%s)", latestRelease.Version, latestRelease.ShortHash) + } + } } diff --git a/internal/api/resolver.go b/internal/api/resolver.go index bfe96939f..69a3a83d4 100644 --- a/internal/api/resolver.go +++ b/internal/api/resolver.go @@ -184,19 +184,22 @@ func (r *queryResolver) Version(ctx context.Context) (*Version, error) { }, nil } -// Latestversion returns the latest git shorthash commit. -func (r *queryResolver) Latestversion(ctx context.Context) (*ShortVersion, error) { - ver, url, err := GetLatestVersion(ctx, true) - if err == nil { - logger.Infof("Retrieved latest hash: %s", ver) - } else { - logger.Errorf("Error while retrieving latest hash: %s", err) +func (r *queryResolver) Latestversion(ctx context.Context) (*LatestVersion, error) { + latestRelease, err := GetLatestRelease(ctx) + if err != nil { + if !errors.Is(err, context.Canceled) { + logger.Errorf("Error while retrieving latest version: %v", err) + } + return nil, err } + logger.Infof("Retrieved latest version: %s (%s)", latestRelease.Version, latestRelease.ShortHash) - return &ShortVersion{ - Shorthash: ver, - URL: url, - }, err + return &LatestVersion{ + Version: latestRelease.Version, + Shorthash: latestRelease.ShortHash, + ReleaseDate: latestRelease.Date, + URL: latestRelease.Url, + }, nil } // Get scene marker tags which show up under the video. diff --git a/internal/api/server.go b/internal/api/server.go index 27b3d7722..22bb14ad0 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path" + "regexp" "runtime/debug" "strconv" "strings" @@ -392,22 +393,48 @@ func javascriptHandler(c *config.Instance, pluginCache *plugin.Cache) func(w htt } func printVersion() { - versionString := githash + var versionString string + switch { + case version != "": + if githash != "" && !IsDevelop() { + versionString = version + " (" + githash + ")" + } else { + versionString = version + } + case githash != "": + versionString = githash + default: + versionString = "unknown" + } if config.IsOfficialBuild() { versionString += " - Official Build" } else { versionString += " - Unofficial Build" } - if version != "" { - versionString = version + " (" + versionString + ")" + if buildstamp != "" { + versionString += " - " + buildstamp } - fmt.Printf("stash version: %s - %s\n", versionString, buildstamp) + logger.Infof("stash version: %s\n", versionString) } func GetVersion() (string, string, string) { return version, githash, buildstamp } +func IsDevelop() bool { + if githash == "" { + return false + } + + // if the version is suffixed with -x-xxxx, then we are running a development build + develop := false + re := regexp.MustCompile(`-\d+-g\w+$`) + if re.MatchString(version) { + develop = true + } + return develop +} + func makeTLSConfig(c *config.Instance) (*tls.Config, error) { c.InitTLS() certFile, keyFile := c.GetTLSFiles() @@ -428,12 +455,12 @@ func makeTLSConfig(c *config.Instance) (*tls.Config, error) { cert, err := os.ReadFile(certFile) if err != nil { - return nil, fmt.Errorf("error reading SSL certificate file %s: %s", certFile, err.Error()) + return nil, fmt.Errorf("error reading SSL certificate file %s: %v", certFile, err) } key, err := os.ReadFile(keyFile) if err != nil { - return nil, fmt.Errorf("error reading SSL key file %s: %s", keyFile, err.Error()) + return nil, fmt.Errorf("error reading SSL key file %s: %v", keyFile, err) } certs := make([]tls.Certificate, 1) diff --git a/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx b/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx index fd2f52906..13d8edde7 100644 --- a/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Button } from "react-bootstrap"; import { useIntl } from "react-intl"; import { useLatestVersion } from "src/core/StashService"; -import { ConstantSetting, Setting, SettingGroup } from "./Inputs"; +import { ConstantSetting, SettingGroup } from "./Inputs"; import { SettingSection } from "./SettingSection"; export const SettingsAboutPanel: React.FC = () => { @@ -20,7 +20,69 @@ export const SettingsAboutPanel: React.FC = () => { networkStatus, } = useLatestVersion(); - const hasNew = dataLatest && gitHash !== dataLatest.latestversion.shorthash; + function renderLatestVersion() { + if (errorLatest) { + return ( + + ); + } else if (!dataLatest || loadingLatest || networkStatus === 4) { + return ( + + ); + } else { + let heading = dataLatest.latestversion.version; + const hashString = dataLatest.latestversion.shorthash; + if (gitHash !== hashString) { + heading += + " " + + intl.formatMessage({ + id: "config.about.new_version_notice", + }); + } + return ( + +
+
+

+ {intl.formatMessage({ + id: "config.about.build_hash", + })} +

+
{hashString}
+
+
+ + + + +
+
+ +
+ ); + } + } return ( <> @@ -39,48 +101,10 @@ export const SettingsAboutPanel: React.FC = () => { value={buildTime} />
+ - - {errorLatest ? ( - - ) : !dataLatest || loadingLatest || networkStatus === 4 ? ( - - ) : ( -
-
-

- {intl.formatMessage({ - id: "config.about.latest_version_build_hash", - })} -

-
- {dataLatest.latestversion.shorthash}{" "} - {hasNew - ? intl.formatMessage({ - id: "config.about.new_version_notice", - }) - : undefined} -
-
-
- - - - -
-
- )} -
+ + {renderLatestVersion()} diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index dc41b2e11..3c81afb4a 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -185,6 +185,7 @@ "latest_version": "Latest Version", "latest_version_build_hash": "Latest Version Build Hash:", "new_version_notice": "[NEW]", + "release_date": "Release date:", "stash_discord": "Join our {url} channel", "stash_home": "Stash home at {url}", "stash_open_collective": "Support us through {url}",