github.com/qjfoidnh/BaiduPCS-Go@v0.0.0-20231011165705-caa18a3765f3/internal/pcsupdate/pcsupdate.go (about)

     1  // Package pcsupdate 更新包
     2  package pcsupdate
     3  
     4  import (
     5  	"archive/zip"
     6  	"bytes"
     7  	"fmt"
     8  	"github.com/qjfoidnh/BaiduPCS-Go/internal/pcsconfig"
     9  	"github.com/qjfoidnh/BaiduPCS-Go/pcsliner"
    10  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil"
    11  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/cachepool"
    12  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/checkaccess"
    13  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/converter"
    14  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/jsonhelper"
    15  	"github.com/qjfoidnh/BaiduPCS-Go/requester/downloader"
    16  	"github.com/qjfoidnh/BaiduPCS-Go/requester/rio"
    17  	"github.com/qjfoidnh/BaiduPCS-Go/requester/transfer"
    18  	"net/http"
    19  	"path/filepath"
    20  	"regexp"
    21  	"runtime"
    22  	"strconv"
    23  	"strings"
    24  )
    25  
    26  const (
    27  	// ReleaseName 分享根目录名称
    28  	ReleaseName = "BaiduPCS-Go-releases"
    29  )
    30  
    31  type info struct {
    32  	filename    string
    33  	size        int64
    34  	downloadURL string
    35  }
    36  
    37  // CheckUpdate 检测更新
    38  func CheckUpdate(version string, yes bool) {
    39  	if !checkaccess.AccessRDWR(pcsutil.ExecutablePath()) {
    40  		fmt.Printf("程序目录不可写, 无法更新.\n")
    41  		return
    42  	}
    43  	fmt.Println("检测更新中, 稍候...")
    44  	c := pcsconfig.Config.HTTPClient()
    45  	resp, err := c.Req(http.MethodGet, "https://api.github.com/repos/qjfoidnh/BaiduPCS-Go/releases/latest", nil, nil)
    46  	if resp != nil {
    47  		defer resp.Body.Close()
    48  	}
    49  	if err != nil {
    50  		fmt.Printf("获取数据错误: %s\n", err)
    51  		return
    52  	}
    53  
    54  	releaseInfo := ReleaseInfo{}
    55  	err = jsonhelper.UnmarshalData(resp.Body, &releaseInfo)
    56  	if err != nil {
    57  		fmt.Printf("json数据解析失败: %s\n", err)
    58  		return
    59  	}
    60  
    61  	// 没有更新, 或忽略 Beta 版本, 和版本前缀不符的
    62  	if strings.Contains(releaseInfo.TagName, "Beta") || !strings.HasPrefix(releaseInfo.TagName, "v") || version >= releaseInfo.TagName {
    63  		fmt.Printf("未检测到更新!\n")
    64  		return
    65  	}
    66  
    67  	fmt.Printf("检测到新版本: %s\n", releaseInfo.TagName)
    68  
    69  	line := pcsliner.NewLiner()
    70  	defer line.Close()
    71  
    72  	if !yes {
    73  		y, err := line.State.Prompt("是否进行更新 (y/n): ")
    74  		if err != nil {
    75  			fmt.Printf("输入错误: %s\n", err)
    76  			return
    77  		}
    78  
    79  		if y != "y" && y != "Y" {
    80  			fmt.Printf("更新取消.\n")
    81  			return
    82  		}
    83  	}
    84  
    85  	builder := &strings.Builder{}
    86  	builder.WriteString("BaiduPCS-Go-" + releaseInfo.TagName + "-" + runtime.GOOS + "-.*?")
    87  	if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
    88  		builder.WriteString("arm")
    89  	} else {
    90  		switch runtime.GOARCH {
    91  		case "amd64":
    92  			builder.WriteString("(amd64|x86_64|x64)")
    93  		case "386":
    94  			builder.WriteString("(386|x86)")
    95  		case "arm":
    96  			builder.WriteString("(armv5|armv7|arm)")
    97  		case "arm64":
    98  			builder.WriteString("arm64")
    99  		case "mips":
   100  			builder.WriteString("mips")
   101  		case "mips64":
   102  			builder.WriteString("mips64")
   103  		case "mipsle":
   104  			builder.WriteString("(mipsle|mipsel)")
   105  		case "mips64le":
   106  			builder.WriteString("(mips64le|mips64el)")
   107  		default:
   108  			builder.WriteString(runtime.GOARCH)
   109  		}
   110  	}
   111  	builder.WriteString("\\.zip")
   112  
   113  	exp := regexp.MustCompile(builder.String())
   114  
   115  	var targetList []*info
   116  	for _, asset := range releaseInfo.Assets {
   117  		if asset == nil || asset.State != "uploaded" {
   118  			continue
   119  		}
   120  
   121  		if exp.MatchString(asset.Name) {
   122  			targetList = append(targetList, &info{
   123  				filename:    asset.Name,
   124  				size:        asset.Size,
   125  				downloadURL: asset.BrowserDownloadURL,
   126  			})
   127  		}
   128  	}
   129  
   130  	var target info
   131  	switch len(targetList) {
   132  	case 0:
   133  		fmt.Printf("未匹配到当前系统的程序更新文件, GOOS: %s, GOARCH: %s\n", runtime.GOOS, runtime.GOARCH)
   134  		return
   135  	case 1:
   136  		target = *targetList[0]
   137  	default:
   138  		fmt.Println()
   139  		for k := range targetList {
   140  			fmt.Printf("%d: %s\n", k, targetList[k].filename)
   141  		}
   142  
   143  		fmt.Println()
   144  		t, err := line.State.Prompt("输入序号以下载更新: ")
   145  		if err != nil {
   146  			fmt.Printf("%s\n", err)
   147  			return
   148  		}
   149  
   150  		i, err := strconv.Atoi(t)
   151  		if err != nil {
   152  			fmt.Printf("输入错误: %s\n", err)
   153  			return
   154  		}
   155  
   156  		if i < 0 || i >= len(targetList) {
   157  			fmt.Printf("输入错误: 序号不在范围内\n")
   158  			return
   159  		}
   160  
   161  		target = *targetList[i]
   162  	}
   163  
   164  	if target.size > 0x7fffffff {
   165  		fmt.Printf("file size too large: %d\n", target.size)
   166  		return
   167  	}
   168  
   169  	fmt.Printf("准备下载更新: %s\n", target.filename)
   170  
   171  	// 开始下载
   172  	buf := rio.NewBuffer(cachepool.RawMallocByteSlice(int(target.size)))
   173  	der := downloader.NewDownloader(target.downloadURL, buf, &downloader.Config{
   174  		MaxParallel: 20,
   175  		CacheSize:   10000,
   176  	})
   177  	der.SetClient(c)
   178  
   179  	der.OnDownloadStatusEvent(func(status transfer.DownloadStatuser, workersCallback func(downloader.RangeWorkerFunc)) {
   180  		var leftStr string
   181  		left := status.TimeLeft()
   182  		if left < 0 {
   183  			leftStr = "-"
   184  		} else {
   185  			leftStr = left.String()
   186  		}
   187  
   188  		fmt.Printf("\r ↓ %s/%s %s/s in %s, left %s ............",
   189  			converter.ConvertFileSize(status.Downloaded(), 2),
   190  			converter.ConvertFileSize(status.TotalSize(), 2),
   191  			converter.ConvertFileSize(status.SpeedsPerSecond(), 2),
   192  			status.TimeElapsed()/1e7*1e7, leftStr,
   193  		)
   194  	})
   195  	der.OnFinish(func() {
   196  		fmt.Println()
   197  	})
   198  	der.OnSuccess(func() {
   199  		fmt.Printf("下载完毕\n")
   200  	})
   201  
   202  	err = der.Execute()
   203  	if err != nil {
   204  		fmt.Printf("下载发生错误: %s\n", err)
   205  		return
   206  	}
   207  
   208  	// 读取文件
   209  	reader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), target.size)
   210  	if err != nil {
   211  		fmt.Printf("读取更新文件发生错误: %s\n", err)
   212  		return
   213  	}
   214  
   215  	execPath := pcsutil.ExecutablePath()
   216  
   217  	var fileNum, errTimes int
   218  	for _, zipFile := range reader.File {
   219  		if zipFile == nil {
   220  			continue
   221  		}
   222  
   223  		info := zipFile.FileInfo()
   224  
   225  		if info.IsDir() {
   226  			continue
   227  		}
   228  
   229  		rc, err := zipFile.Open()
   230  		if err != nil {
   231  			fmt.Printf("解析 zip 文件错误: %s\n", err)
   232  			continue
   233  		}
   234  
   235  		fileNum++
   236  
   237  		name := zipFile.Name[strings.Index(zipFile.Name, "/")+1:]
   238  		if name == "BaiduPCS-Go" {
   239  			err = update(pcsutil.Executable(), rc)
   240  		} else {
   241  			err = update(filepath.Join(execPath, name), rc)
   242  		}
   243  
   244  		if err != nil {
   245  			errTimes++
   246  			fmt.Printf("发生错误, zip 路径: %s, 错误: %s\n", zipFile.Name, err)
   247  			continue
   248  		}
   249  	}
   250  
   251  	if errTimes == fileNum {
   252  		fmt.Printf("更新失败\n")
   253  		return
   254  	}
   255  
   256  	fmt.Printf("更新完毕, 请重启程序\n")
   257  }