study_xxqg/utils/update/update.go

274 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}