-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support for new version detection feature. (#688)
Signed-off-by: CFC4N <cfc4n.cs@gmail.com>
- Loading branch information
Showing
7 changed files
with
433 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/gojue/ecapture/pkg/upgrade" | ||
"golang.org/x/sys/unix" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
const urlReleases = "https://api.github.com/repos/gojue" | ||
const urlReleasesCN = "https://image.cnxct.com" | ||
const apiReleases string = "/ecapture/releases/latest" | ||
|
||
var ( | ||
ErrOsArchNotFound = errors.New("new tag found, but no os/arch match") | ||
ErrAheadOfLatestVersion = errors.New("local version is ahead of latest version") | ||
) | ||
|
||
func upgradeCheck(ctx context.Context) (string, string, error) { | ||
|
||
// uname -a | ||
var uname unix.Utsname | ||
err := unix.Uname(&uname) | ||
if err != nil { | ||
return "", "", fmt.Errorf("Error getting uname: %v", err) | ||
} | ||
var useragent = fmt.Sprintf("eCapture Cli (%s %s %s)", | ||
byteToString(uname.Sysname[:]), // 系统名称 | ||
byteToString(uname.Release[:]), // 版本号 | ||
byteToString(uname.Machine[:]), // 机器类型 | ||
) | ||
var arch = "amd64" | ||
if byteToString(uname.Machine[:]) == "aarch64" { | ||
arch = "arm64" | ||
} | ||
rex := regexp.MustCompile(`([^:]+):v?(\d+\.\d+\.\d+)[^:]+:[^:]+`) | ||
verMatch := rex.FindStringSubmatch(GitVersion) | ||
if len(verMatch) <= 2 { | ||
return "", "", fmt.Errorf("error matching version: %s, verMatch:%v", GitVersion, verMatch) | ||
} | ||
var os = "linux" | ||
if strings.Contains(verMatch[1], "androidgki") { | ||
os = "android" | ||
} | ||
githubResp, err := upgrade.GetLatestVersion(useragent, fmt.Sprintf("%s%s?ver=%s", urlReleasesCN, apiReleases, GitVersion), ctx) | ||
if err != nil { | ||
return "", "", fmt.Errorf("error getting latest version: %v", err) | ||
} | ||
|
||
comp, err := upgrade.CheckVersion(verMatch[2], githubResp.TagName) | ||
if err != nil { | ||
return "", "", fmt.Errorf("error checking version: %v", err) | ||
} | ||
|
||
if comp >= 0 { | ||
return "", "", ErrAheadOfLatestVersion | ||
} | ||
|
||
// "name": "ecapture-v0.8.12-android-amd64.tar.gz", | ||
var targetAsset = fmt.Sprintf("ecapture-%s-%s-%s.tar.gz", githubResp.TagName, os, arch) | ||
for _, asset := range githubResp.Assets { | ||
if asset.Name == targetAsset { | ||
return githubResp.TagName, asset.BrowserDownloadURL, nil | ||
} | ||
} | ||
return "", "", ErrOsArchNotFound | ||
} | ||
|
||
func byteToString(b []byte) string { | ||
n := 0 | ||
for n < len(b) && b[n] != 0 { | ||
n++ | ||
} | ||
return string(b[:n]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Copyright 2024 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package upgrade | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// from https://api.github.com/repos/gojue/ecapture/releases/latest | ||
// https://developer.github.com/v3/repos/releases/ | ||
|
||
// GithubReleaseResp data related to a release. | ||
type GithubReleaseResp struct { | ||
URL string `json:"url"` | ||
AssetsURL string `json:"assets_url"` | ||
UploadURL string `json:"upload_url"` | ||
HTMLURL string `json:"html_url"` | ||
ID int64 `json:"id"` | ||
Author Author `json:"author"` | ||
NodeID string `json:"node_id"` | ||
TagName string `json:"tag_name"` | ||
TargetCommitish string `json:"target_commitish"` | ||
Name string `json:"name"` | ||
Draft bool `json:"draft"` | ||
Prerelease bool `json:"prerelease"` | ||
CreatedAt time.Time `json:"created_at"` | ||
PublishedAt time.Time `json:"published_at"` | ||
Assets []ReleaseAsset `json:"assets"` | ||
TarballURL string `json:"tarball_url"` | ||
ZipballURL string `json:"zipball_url"` | ||
Body string `json:"body"` | ||
MentionsCount int `json:"mentions_count"` | ||
} | ||
|
||
type Author struct { | ||
Login string `json:"login"` | ||
ID int64 `json:"id"` | ||
NodeID string `json:"node_id"` | ||
AvatarURL string `json:"avatar_url"` | ||
GravatarID string `json:"gravatar_id"` | ||
URL string `json:"url"` | ||
HTMLURL string `json:"html_url"` | ||
FollowersURL string `json:"followers_url"` | ||
FollowingURL string `json:"following_url"` | ||
GistsURL string `json:"gists_url"` | ||
StarredURL string `json:"starred_url"` | ||
SubscriptionsURL string `json:"subscriptions_url"` | ||
OrganizationsURL string `json:"organizations_url"` | ||
ReposURL string `json:"repos_url"` | ||
EventsURL string `json:"events_url"` | ||
ReceivedEventsURL string `json:"received_events_url"` | ||
Type string `json:"type"` | ||
UserViewType string `json:"user_view_type"` | ||
SiteAdmin bool `json:"site_admin"` | ||
} | ||
|
||
// ReleaseAsset data related to a release asset. | ||
type ReleaseAsset struct { | ||
URL string `json:"url"` | ||
ID int64 `json:"id"` | ||
NodeID string `json:"node_id"` | ||
Name string `json:"name"` | ||
Label string `json:"label"` | ||
Uploader Author `json:"uploader"` | ||
ContentType string `json:"content_type"` | ||
State string `json:"state"` | ||
Size int64 `json:"size"` | ||
DownloadCount int `json:"download_count"` | ||
CreatedAt time.Time `json:"created_at"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
BrowserDownloadURL string `json:"browser_download_url"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright 2024 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package upgrade | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
/* | ||
https://api.github.com/repos/gojue/ecapture/releases/latest | ||
https://api.github.com/repos/gojue/ecapture/releases/tags/v0.1.0 | ||
https://github.com/gojue/ecapture/releases/download/v0.8.12/checksum-v0.8.12.txt | ||
image.cnxct.com/ecapture/releases/latest | ||
image.cnxct.com/ecapture/releases/tags/v0.1.0 | ||
image.cnxct.com/ecapture/download/v0.8.12/checksum-v0.8.12.txt | ||
*/ | ||
|
||
// we use the GitHub REST V3 API as no login is required | ||
// https://docs.github.com/zh/rest/using-the-rest-api | ||
|
||
func GetLatestVersion(ua, url string, ctx context.Context) (GithubReleaseResp, error) { | ||
var release GithubReleaseResp | ||
err := makeGithubRequest(ctx, ua, url, &release) | ||
if err != nil { | ||
return release, err | ||
} | ||
|
||
return release, nil | ||
|
||
} | ||
|
||
func CheckVersion(localVer, remoteVer string) (int, error) { | ||
|
||
localVer = strings.ReplaceAll(localVer, "v", "") | ||
remoteVer = strings.ReplaceAll(remoteVer, "v", "") | ||
|
||
v1, err := ParseVersion(localVer) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
v2, err := ParseVersion(remoteVer) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
comparison := CompareVersions(v1, v2) | ||
return comparison, nil | ||
} | ||
|
||
func makeGithubRequest(ctx context.Context, ua, url string, output interface{}) error { | ||
transport := &http.Transport{Proxy: http.ProxyFromEnvironment} | ||
|
||
client := &http.Client{ | ||
Timeout: 3 * time.Second, | ||
Transport: transport, | ||
} | ||
|
||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | ||
|
||
req.Header.Add("Accept", "application/json") // gh api recommendation , send header with api version | ||
req.Header.Set("User-Agent", ua) // eCapture Cli Linux 5.15.0-125-generic aarch64 | ||
response, err := client.Do(req) | ||
if err != nil { | ||
//lint:ignore ST1005 Github is a proper capitalized noun | ||
return fmt.Errorf("API request failed: %w", err) | ||
} | ||
|
||
if response.StatusCode != http.StatusOK { | ||
//lint:ignore ST1005 Github is a proper capitalized noun | ||
return fmt.Errorf("API request failed, statusCOde: %s", response.Status) | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
data, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
//lint:ignore ST1005 Github is a proper capitalized noun | ||
return fmt.Errorf("API read response failed: %w", err) | ||
} | ||
|
||
err = json.Unmarshal(data, output) | ||
if err != nil { | ||
return fmt.Errorf("unmarshalling Github API response failed: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Version 结构体表示一个版本号 | ||
type Version struct { | ||
Major int | ||
Minor int | ||
Patch int | ||
} | ||
|
||
// ParseVersion 解析版本号字符串 | ||
func ParseVersion(versionStr string) (Version, error) { | ||
parts := strings.Split(versionStr, ".") | ||
if len(parts) != 3 { | ||
return Version{}, fmt.Errorf("invalid version format") | ||
} | ||
|
||
major, err := strconv.Atoi(parts[0]) | ||
if err != nil { | ||
return Version{}, err | ||
} | ||
minor, err := strconv.Atoi(parts[1]) | ||
if err != nil { | ||
return Version{}, err | ||
} | ||
patch, err := strconv.Atoi(parts[2]) | ||
if err != nil { | ||
return Version{}, err | ||
} | ||
|
||
return Version{Major: major, Minor: minor, Patch: patch}, nil | ||
} | ||
|
||
// CompareVersions 比较两个版本号 | ||
func CompareVersions(v1, v2 Version) int { | ||
if v1.Major != v2.Major { | ||
return v1.Major - v2.Major | ||
} | ||
if v1.Minor != v2.Minor { | ||
return v1.Minor - v2.Minor | ||
} | ||
return v1.Patch - v2.Patch | ||
} |
Oops, something went wrong.