From 5a1d99989fa32be1684b9290b4a58e8418b7b92d Mon Sep 17 00:00:00 2001
From: shenwc <spd260@126.com>
Date: 星期二, 26 十一月 2024 17:57:50 +0800
Subject: [PATCH] 11111

---
 update.go |  267 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 267 insertions(+), 0 deletions(-)

diff --git a/update.go b/update.go
new file mode 100755
index 0000000..69334d4
--- /dev/null
+++ b/update.go
@@ -0,0 +1,267 @@
+// package goupdate provides tooling to auto-update binary releases
+// from GitHub based on the user's current version and operating system.
+package goupdate
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/c4milo/unpackit"
+	"github.com/pkg/errors"
+	"github.com/zan8in/gologger"
+)
+
+// Proxy is used to proxy a reader, for example
+// using https://github.com/cheggaaa/pb to provide
+// progress updates.
+type Proxy func(int, io.ReadCloser) io.ReadCloser
+
+// NopProxy does nothing.
+var NopProxy = func(size int, r io.ReadCloser) io.ReadCloser {
+	return r
+}
+
+// Manager is the update manager.
+type Manager struct {
+	Store          // Store for releases such as Github or a custom private store.
+	Command string // Command is the executable's name.
+}
+
+// Release represents a project release.
+type Release struct {
+	Version     string    // Version is the release version.
+	Notes       string    // Notes is the markdown release notes.
+	URL         string    // URL is the notes url.
+	PublishedAt time.Time // PublishedAt is the publish time.
+	Assets      []*Asset  // Assets is the release assets.
+}
+
+// Asset represents a project release asset.
+type Asset struct {
+	Name          string // Name of the asset.
+	Size          int    // Size of the asset.
+	URL           string // URL of the asset.
+	Downloads     int    // Downloads count.
+	LatestVersion string
+}
+
+// InstallTo binary to the given dir.
+func (m *Manager) InstallTo(path, dir string) error {
+	gologger.Debug().Msgf("unpacking %q", path)
+
+	f, err := os.Open(path)
+	if err != nil {
+		return errors.Wrap(err, "opening tarball")
+	}
+
+	tempdir := filepath.Join(dir, "tmp")
+	err = unpackit.Unpack(f, tempdir)
+	if err != nil {
+		f.Close()
+		return errors.Wrap(err, "unpacking tarball")
+	}
+	defer os.RemoveAll(tempdir)
+
+	if err := f.Close(); err != nil {
+		return errors.Wrap(err, "closing tarball")
+	}
+
+	latestBinary := filepath.Join(tempdir, m.Command)
+
+	if err := os.Chmod(latestBinary, 0755); err != nil {
+		return errors.Wrap(err, "chmod")
+	}
+
+	currentBinary := filepath.Join(dir, m.Command)
+	latestBinaryTmp := currentBinary + ".tmp"
+
+	gologger.Debug().Msgf("copy %q to %q", latestBinary, latestBinaryTmp)
+	if err := copyFile(latestBinaryTmp, latestBinary); err != nil {
+		return errors.Wrap(err, "copying")
+	}
+
+	if runtime.GOOS == "windows" {
+		old := currentBinary + ".old"
+		gologger.Debug().Msgf("windows workaround renaming %q to %q", currentBinary, old)
+		if err := os.Rename(currentBinary, old); err != nil {
+			return errors.Wrap(err, "windows renaming")
+		}
+	}
+
+	gologger.Debug().Msgf("renaming %q to %q", latestBinaryTmp, currentBinary)
+	if err := os.Rename(latestBinaryTmp, currentBinary); err != nil {
+		return errors.Wrap(err, "renaming")
+	}
+
+	return nil
+}
+
+// Install binary to replace the current version.
+func (m *Manager) Install(path string) error {
+	// bin, err := exec.LookPath(m.Command) // 获取当前程序的绝对路径
+	// if err != nil {
+	// 	return errors.Wrapf(err, "looking up path of %q", m.Command)
+	// }
+
+	// dir := filepath.Dir(bin)
+	dir, err := getExecutablePath()
+	if err != nil {
+		return errors.Wrapf(err, "looking up path of %q", m.Command)
+	}
+	return m.InstallTo(path, dir)
+}
+
+// FindTarball returns a tarball matching os and arch, or nil.
+func (r *Release) FindTarball(os, arch string) *Asset {
+	s := fmt.Sprintf("%s_%s", os, arch)
+	for _, a := range r.Assets {
+		ext := filepath.Ext(a.Name)
+		if strings.Contains(a.Name, s) && ext == ".gz" {
+			return a
+		}
+	}
+
+	return nil
+}
+
+// FindZip returns a zipfile matching os and arch, or nil.
+func (r *Release) FindZip(os, arch string) *Asset {
+	s := fmt.Sprintf("%s_%s", os, arch)
+	for _, a := range r.Assets {
+		ext := filepath.Ext(a.Name)
+		if strings.Contains(a.Name, s) && ext == ".zip" {
+			return a
+		}
+	}
+
+	return nil
+}
+
+// Download the asset to a tmp directory and return its path.
+func (a *Asset) Download() (string, error) {
+	return a.DownloadProxy(NopProxy)
+}
+
+// DownloadProxy the asset to a tmp directory and return its path.
+func (a *Asset) DownloadProxy(proxy Proxy) (string, error) {
+	f, err := os.CreateTemp(os.TempDir(), "update-")
+	if err != nil {
+		return "", errors.Wrap(err, "creating temp file")
+	}
+
+	gologger.Debug().Msgf("fetch %q", a.URL)
+	res, err := http.Get(a.URL)
+	if err != nil {
+		return "", errors.Wrap(err, "fetching asset")
+	}
+
+	kind := res.Header.Get("Content-Type")
+	size, _ := strconv.Atoi(res.Header.Get("Content-Length"))
+	gologger.Debug().Msgf("response %s – %s (%d KiB)", res.Status, kind, size/1024)
+
+	body := proxy(size, res.Body)
+
+	if res.StatusCode >= 400 {
+		body.Close()
+		return "", errors.Wrap(err, res.Status)
+	}
+
+	gologger.Debug().Msgf("copy to %q", f.Name())
+	if _, err := io.Copy(f, body); err != nil {
+		body.Close()
+		return "", errors.Wrap(err, "copying body")
+	}
+
+	if err := body.Close(); err != nil {
+		return "", errors.Wrap(err, "closing body")
+	}
+
+	if err := f.Close(); err != nil {
+		return "", errors.Wrap(err, "closing file")
+	}
+
+	gologger.Debug().Msgf("copied")
+	return f.Name(), nil
+}
+
+// copyFile copies the contents of the file named src to the file named
+// by dst. The file will be created if it does not already exist. If the
+// destination file exists, all it's contents will be replaced by the contents
+// of the source file. The file mode will be copied from the source and
+// the copied data is synced/flushed to stable storage.
+func copyFile(dst, src string) (err error) {
+	in, err := os.Open(src)
+	if err != nil {
+		return
+	}
+	defer in.Close()
+
+	out, err := os.Create(dst)
+	if err != nil {
+		return
+	}
+
+	defer func() {
+		if e := out.Close(); e != nil {
+			err = e
+		}
+	}()
+
+	_, err = io.Copy(out, in)
+	if err != nil {
+		return
+	}
+
+	err = out.Sync()
+	if err != nil {
+		return
+	}
+
+	si, err := os.Stat(src)
+	if err != nil {
+		return
+	}
+
+	err = os.Chmod(dst, si.Mode())
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func getExecutablePath() (string, error) {
+	exePath, err := os.Executable()
+	if err != nil {
+		return exePath, err
+	}
+
+	// 检查可执行文件是否存在
+	_, err = os.Stat(exePath)
+	if os.IsNotExist(err) {
+		// 可执行文件不存在,采取适当的处理措施
+		// 例如记录错误信息或提供备用路径
+		return exePath, err
+	}
+
+	// 处理相对路径
+	if !filepath.IsAbs(exePath) {
+		exePath, err = filepath.Abs(exePath)
+		if err != nil {
+			// 处理转换为绝对路径时的错误
+			return exePath, err
+		}
+	}
+
+	// 获取可执行文件所在目录的绝对路径
+	exeDir := filepath.Dir(exePath)
+
+	return exeDir, err
+}

--
Gitblit v1.8.0