From 87f0b667b5cf365fe065306923898f006f07839d Mon Sep 17 00:00:00 2001 From: bnkai <48220860+bnkai@users.noreply.github.com> Date: Wed, 8 Jan 2020 00:21:23 +0200 Subject: [PATCH] Add check version functionality (#296) * Add check version functionality * add backend support * add ui support * minor fixes * cosmetic fixes * workaround query refetch not working after network error * revert changes to Makefile after testing is complete * switch to Releases Github API endpoint, add latest Release URL to UI * latest version is only shown in UI when version is available and data is ready * resolve conflict , squash rebase --- graphql/documents/queries/misc.graphql | 7 + graphql/schema/schema.graphql | 5 +- graphql/schema/types/version.graphql | 7 +- pkg/api/check_version.go | 179 ++++++++++++++++++ pkg/api/resolver.go | 17 +- pkg/api/server.go | 2 + .../Settings/SettingsAboutPanel.tsx | 73 +++++-- ui/v2/src/core/StashService.ts | 76 ++++---- 8 files changed, 311 insertions(+), 55 deletions(-) create mode 100644 pkg/api/check_version.go diff --git a/graphql/documents/queries/misc.graphql b/graphql/documents/queries/misc.graphql index 2311c2e6b..65b0a59e1 100644 --- a/graphql/documents/queries/misc.graphql +++ b/graphql/documents/queries/misc.graphql @@ -66,3 +66,10 @@ query Version { build_time } } + +query LatestVersion { + latestversion { + shorthash + url + } +} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 03d7b88f1..099fc2d78 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -96,6 +96,9 @@ type Query { # Version version: Version! + + # LatestVersion + latestversion: ShortVersion! } type Mutation { @@ -136,4 +139,4 @@ schema { query: Query mutation: Mutation subscription: Subscription -} \ No newline at end of file +} diff --git a/graphql/schema/types/version.graphql b/graphql/schema/types/version.graphql index 0c275b6ad..305c431cf 100644 --- a/graphql/schema/types/version.graphql +++ b/graphql/schema/types/version.graphql @@ -2,4 +2,9 @@ type Version { version: String hash: String! build_time: String! -} \ No newline at end of file +} + +type ShortVersion { + shorthash: String! + url: String! +} diff --git a/pkg/api/check_version.go b/pkg/api/check_version.go new file mode 100644 index 000000000..c29fd6b23 --- /dev/null +++ b/pkg/api/check_version.go @@ -0,0 +1,179 @@ +package api + +import ( + "encoding/json" + "fmt" + "github.com/stashapp/stash/pkg/logger" + "io/ioutil" + "net/http" + "runtime" + "time" +) + +//we use the github REST V3 API as no login is required +const apiURL string = "https://api.github.com/repos/stashapp/stash/tags" +const apiReleases string = "https://api.github.com/repos/stashapp/stash/releases" +const apiAcceptHeader string = "application/vnd.github.v3+json" + +var stashReleases = func() map[string]string { + return map[string]string{ + "windows/amd64": "stash-win.exe", + "linux/amd64": "stash-linux", + "darwin/amd64": "stash-osx", + "linux/arm": "stash-pi", + } +} + +type githubTagResponse struct { + Name string + Zipball_url string + Tarball_url string + Commit struct { + Sha string + Url string + } + Node_id string +} + +type githubReleasesResponse struct { + Url string + Assets_url string + Upload_url string + Html_url string + Id int64 + Node_id string + Tag_name string + Target_commitish string + Name string + Draft bool + Author githubAuthor + Prerelease bool + Created_at string + Published_at string + Assets []githubAsset + Tarball_url string + Zipball_url string + Body string +} + +type githubAuthor struct { + Login string + Id int64 + Node_id string + Avatar_url string + Gravatar_id string + Url string + Html_url string + Followers_url string + Following_url string + Gists_url string + Starred_url string + Subscriptions_url string + Organizations_url string + Repos_url string + Events_url string + Received_events_url string + Type string + Site_admin bool +} + +type githubAsset struct { + Url string + Id int64 + Node_id string + Name string + Label string + Uploader githubAuthor + Content_type string + State string + Size int64 + Download_count int64 + Created_at string + Updated_at string + Browser_download_url string +} + +//gets latest version (git commit hash) from github API +//the repo's tags are used to find the latest version +//of the "master" or "develop" branch +func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease string, err error) { + + platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) + wantedRelease := stashReleases()[platform] + + branch, _, _ := GetVersion() + if branch == "" { + return "", "", fmt.Errorf("Stash doesn't have a version. Version check not supported.") + } + + client := &http.Client{ + Timeout: 3 * time.Second, + } + + req, _ := http.NewRequest("GET", apiReleases, nil) + + req.Header.Add("Accept", apiAcceptHeader) // gh api recommendation , send header with api version + response, err := client.Do(req) + + input := make([]githubReleasesResponse, 0) + + if err != nil { + return "", "", fmt.Errorf("Github API request failed: %s", err) + } else { + + defer response.Body.Close() + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", "", fmt.Errorf("Github API read response failed: %s", err) + } else { + err = json.Unmarshal(data, &input) + if err != nil { + return "", "", fmt.Errorf("Unmarshalling Github API response failed: %s", err) + } else { + + for _, ghApi := range input { + if ghApi.Tag_name == branch { + + if shortHash { + latestVersion = ghApi.Target_commitish[0:7] //shorthash is first 7 digits of git commit hash + } else { + latestVersion = ghApi.Target_commitish + } + if wantedRelease != "" { + for _, asset := range ghApi.Assets { + if asset.Name == wantedRelease { + latestRelease = asset.Browser_download_url + break + } + + } + } + break + } + } + + } + } + if latestVersion == "" { + return "", "", fmt.Errorf("No version found for \"%s\"", branch) + } + } + return latestVersion, latestRelease, nil + +} + +func printLatestVersion() { + _, githash, _ = GetVersion() + latest, _, err := GetLatestVersion(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) + } + } + +} diff --git a/pkg/api/resolver.go b/pkg/api/resolver.go index 40fcf1e9b..126003a6f 100644 --- a/pkg/api/resolver.go +++ b/pkg/api/resolver.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/99designs/gqlgen/graphql" - + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" ) @@ -116,6 +116,21 @@ func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) { }, nil } +//Gets latest version (git shorthash commit for now) +func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion, error) { + ver, url, err := GetLatestVersion(true) + if err == nil { + logger.Infof("Retrieved latest hash: %s", ver) + } else { + logger.Errorf("Error while retrieving latest hash: %s", err) + } + + return &models.ShortVersion{ + Shorthash: ver, + URL: url, + }, err +} + // Get scene marker tags which show up under the video. func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*models.SceneMarkerTag, error) { sceneID, _ := strconv.Atoi(scene_id) diff --git a/pkg/api/server.go b/pkg/api/server.go index fc759d09e..20d7fa7a7 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -216,6 +216,7 @@ func Start() { go func() { printVersion() + printLatestVersion() logger.Infof("stash is listening on " + address) logger.Infof("stash is running at https://" + displayAddress + "/") logger.Fatal(httpsServer.ListenAndServeTLS("", "")) @@ -228,6 +229,7 @@ func Start() { go func() { printVersion() + printLatestVersion() logger.Infof("stash is listening on " + address) logger.Infof("stash is running at http://" + displayAddress + "/") logger.Fatal(server.ListenAndServe()) diff --git a/ui/v2/src/components/Settings/SettingsAboutPanel.tsx b/ui/v2/src/components/Settings/SettingsAboutPanel.tsx index f995b0552..e428ee62a 100644 --- a/ui/v2/src/components/Settings/SettingsAboutPanel.tsx +++ b/ui/v2/src/components/Settings/SettingsAboutPanel.tsx @@ -1,4 +1,5 @@ import { + Button, H1, H4, H6, @@ -11,10 +12,11 @@ import * as GQL from "../../core/generated-graphql"; import { TextUtils } from "../../utils/text"; import { StashService } from "../../core/StashService"; -interface IProps {} +interface IProps { } export const SettingsAboutPanel: FunctionComponent = (props: IProps) => { const { data, error, loading } = StashService.useVersion(); + const { data: dataLatest, error: errorLatest, loading: loadingLatest, refetch, networkStatus } = StashService.useLatestVersion(); function maybeRenderTag() { if (!data || !data.version || !data.version.version) { return; } @@ -26,23 +28,61 @@ export const SettingsAboutPanel: FunctionComponent = (props: IProps) => ); } + function maybeRenderLatestVersion() { + if (!dataLatest || !dataLatest.latestversion || !dataLatest.latestversion.shorthash || !dataLatest.latestversion.url) { return; } + if (!data || !data.version || !data.version.hash) { + return ( + <>{dataLatest.latestversion.shorthash} + ); + } + + if (data.version.hash !== dataLatest.latestversion.shorthash) { + return ( + <> + {dataLatest.latestversion.shorthash} [NEW] Download + + ); + } + + return ( + <>{dataLatest.latestversion.shorthash} + ); + } + + function renderLatestVersion() { + if (!data || !data.version || !data.version.version) { return; } //if there is no "version" latest version check is obviously not supported + return ( + + + + Latest Version Build Hash: + {maybeRenderLatestVersion()} + + +