274 lines
7.1 KiB
Go
274 lines
7.1 KiB
Go
package update
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"hash"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/dustin/go-humanize"
|
||
"github.com/kardianos/osext"
|
||
log "github.com/sirupsen/logrus"
|
||
"github.com/tidwall/gjson"
|
||
|
||
"github.com/sjkhsl/study_xxqg/conf"
|
||
)
|
||
|
||
// CheckUpdate 检查更新
|
||
func CheckUpdate(version string) string {
|
||
log.Infof("正在检查更新.")
|
||
if version == "(devel)" {
|
||
log.Warnf("检查更新失败: 使用的 Actions 测试版或自编译版本.")
|
||
return ""
|
||
}
|
||
if version == "unknown" {
|
||
log.Warnf("检查更新失败: 使用的未知版本.")
|
||
return ""
|
||
}
|
||
|
||
if !strings.HasPrefix(version, "v") {
|
||
log.Warnf("版本格式错误")
|
||
return ""
|
||
}
|
||
latest, err := lastVersion()
|
||
if err != nil {
|
||
log.Warnf("检查更新失败: %v", err)
|
||
return ""
|
||
}
|
||
if strings.Contains(latest, "must") {
|
||
log.Infoln("检测到强制更新,开始强制更新")
|
||
SelfUpdate("", version)
|
||
return ""
|
||
}
|
||
if versionCompare(version, latest) {
|
||
log.Infof("当前有更新的 study_xxqg 可供更新, 请前往 https://github.com/sjkhsl/study_xxqg/releases 下载.")
|
||
log.Infof("当前版本: %v 最新版本: %v", version, latest)
|
||
return "检测到可用更新,版本号:" + latest
|
||
}
|
||
log.Infof("检查更新完成. 当前已运行最新版本.")
|
||
return ""
|
||
}
|
||
|
||
func readLine() (str string) {
|
||
console := bufio.NewReader(os.Stdin)
|
||
str, _ = console.ReadString('\n')
|
||
str = strings.TrimSpace(str)
|
||
return
|
||
}
|
||
|
||
func lastVersion() (string, error) {
|
||
response, err := http.Get("https://api.github.com/repos/sjkhsl/study_xxqg/releases/latest")
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
data, _ := io.ReadAll(response.Body)
|
||
defer response.Body.Close()
|
||
return gjson.GetBytes(data, "tag_name").Str, nil
|
||
}
|
||
|
||
//
|
||
// versionCompare
|
||
// @Description: 检测是否有更新,有返回true
|
||
// @param nowVersion
|
||
// @param lastVersion
|
||
// @return bool
|
||
//
|
||
func versionCompare(nowVersion, lastVersion string) bool {
|
||
nowVersion = strings.ReplaceAll(nowVersion, "must", "beta")
|
||
lastVersion = strings.ReplaceAll(lastVersion, "must", "beta")
|
||
|
||
NowBeta := strings.Contains(nowVersion, "beta")
|
||
LastBeta := strings.Contains(lastVersion, "beta")
|
||
|
||
// 获取主要版本号
|
||
nowMainVersion := strings.Split(nowVersion, "-")
|
||
lastMainVersion := strings.Split(lastVersion, "-")
|
||
|
||
nowMainIntVersion, _ := strconv.Atoi(strings.ReplaceAll(strings.TrimLeft(nowMainVersion[0], "v"), ".", ""))
|
||
lastMainIntVersion, _ := strconv.Atoi(strings.ReplaceAll(strings.TrimLeft(lastMainVersion[0], "v"), ".", ""))
|
||
|
||
if nowMainIntVersion < lastMainIntVersion {
|
||
return true
|
||
}
|
||
if strings.Contains(nowVersion, "SNAPSHOT") {
|
||
if nowMainIntVersion == lastMainIntVersion {
|
||
return false
|
||
} else {
|
||
return true
|
||
}
|
||
}
|
||
// 如果最新版本是beta
|
||
if LastBeta {
|
||
// 如果当前版本也是beta
|
||
if NowBeta {
|
||
// 对beta后面的数字进行比较
|
||
nowBetaVersion, _ := strconv.Atoi(strings.TrimLeft(nowMainVersion[1], "beta"))
|
||
lastBetaVersion, _ := strconv.Atoi(strings.TrimLeft(lastMainVersion[1], "beta"))
|
||
if nowBetaVersion < lastBetaVersion {
|
||
return true
|
||
}
|
||
return false
|
||
// 如果当前版本部署beta,需要更新
|
||
} else {
|
||
return true
|
||
}
|
||
// 最新版本不是beta,需要更新
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
|
||
func binaryName() string {
|
||
goarch := runtime.GOARCH
|
||
if goarch == "arm" {
|
||
goarch += "v7"
|
||
}
|
||
ext := "tar.gz"
|
||
if runtime.GOOS == "windows" {
|
||
ext = "zip"
|
||
}
|
||
return fmt.Sprintf("study_xxqg_%v_%v.%v", runtime.GOOS, goarch, ext)
|
||
}
|
||
|
||
func checksum(github, version string) []byte {
|
||
sumURL := fmt.Sprintf("%v/sjkhsl/study_xxqg/releases/download/%v/study_xxqg_checksums.txt", github, version)
|
||
response, err := http.Get(sumURL)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
rd := bufio.NewReader(response.Body)
|
||
for {
|
||
str, err := rd.ReadString('\n')
|
||
if err != nil {
|
||
break
|
||
}
|
||
str = strings.TrimSpace(str)
|
||
if strings.HasSuffix(str, binaryName()) {
|
||
sum, _ := hex.DecodeString(strings.TrimSuffix(str, " "+binaryName()))
|
||
return sum
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func wait() {
|
||
log.Info("按 Enter 继续....")
|
||
readLine()
|
||
os.Exit(0)
|
||
}
|
||
|
||
// SelfUpdate 自更新
|
||
func SelfUpdate(github string, version string) {
|
||
github = conf.GetConfig().GithubProxy
|
||
if github == "" {
|
||
github = "https://github.com"
|
||
}
|
||
|
||
if version == "unknown" {
|
||
log.Warningln("测试版本,不更新!")
|
||
return
|
||
}
|
||
|
||
log.Infof("正在检查更新.")
|
||
latest, err := lastVersion()
|
||
if err != nil {
|
||
log.Warnf("获取最新版本失败: %v", err)
|
||
wait()
|
||
}
|
||
url := fmt.Sprintf("%v/sjkhsl/study_xxqg/releases/download/%v/%v", github, latest, binaryName())
|
||
if version == latest {
|
||
log.Info("当前版本已经是最新版本!")
|
||
wait()
|
||
}
|
||
log.Info("当前最新版本为 ", latest)
|
||
log.Info("正在更新,请稍等...")
|
||
sum := checksum(github, latest)
|
||
if sum != nil {
|
||
err = update(url, sum)
|
||
if err != nil {
|
||
log.Error("更新失败: ", err)
|
||
} else {
|
||
log.Info("更新成功!")
|
||
}
|
||
} else {
|
||
log.Error("checksum 失败!")
|
||
}
|
||
}
|
||
|
||
// writeSumCounter 写入量计算实例
|
||
type writeSumCounter struct {
|
||
total uint64
|
||
hash hash.Hash
|
||
}
|
||
|
||
// Write 方法将写入的byte长度追加至写入的总长度Total中
|
||
func (wc *writeSumCounter) Write(p []byte) (int, error) {
|
||
n := len(p)
|
||
wc.total += uint64(n)
|
||
wc.hash.Write(p)
|
||
fmt.Printf("\r ")
|
||
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.total))
|
||
return n, nil
|
||
}
|
||
|
||
// FromStream copy form getlantern/go-update
|
||
func fromStream(updateWith io.Reader) (err error, errRecover error) {
|
||
updatePath, err := osext.Executable()
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
// get the directory the executable exists in
|
||
updateDir := filepath.Dir(updatePath)
|
||
filename := filepath.Base(updatePath)
|
||
// Copy the contents of of newbinary to a the new executable file
|
||
newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
|
||
fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// We won't log this error, because it's always going to happen.
|
||
defer func() { _ = fp.Close() }()
|
||
if _, err = io.Copy(fp, bufio.NewReader(updateWith)); err != nil {
|
||
log.Errorf("Unable to copy data: %v\n", err)
|
||
}
|
||
|
||
// if we don't call fp.Close(), windows won't let us move the new executable
|
||
// because the file will still be "in use"
|
||
if err := fp.Close(); err != nil {
|
||
log.Errorf("Unable to close file: %v\n", err)
|
||
}
|
||
// this is where we'll move the executable to so that we can swap in the updated replacement
|
||
oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
|
||
|
||
// delete any existing old exec file - this is necessary on Windows for two reasons:
|
||
// 1. after a successful update, Windows can't remove the .old file because the process is still running
|
||
// 2. windows rename operations fail if the destination file already exists
|
||
_ = os.Remove(oldPath)
|
||
|
||
// move the existing executable to a new file in the same directory
|
||
err = os.Rename(updatePath, oldPath)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
// move the new executable in to become the new program
|
||
err = os.Rename(newPath, updatePath)
|
||
|
||
if err != nil {
|
||
// copy unsuccessful
|
||
errRecover = os.Rename(oldPath, updatePath)
|
||
} else {
|
||
// copy successful, remove the old binary
|
||
_ = os.Remove(oldPath)
|
||
}
|
||
return
|
||
}
|