| | |
| | | import ( |
| | | "fmt" |
| | | "io" |
| | | "io/ioutil" |
| | | "net/http" |
| | | "os" |
| | | "path/filepath" |
| | |
| | | "strings" |
| | | "time" |
| | | |
| | | "github.com/apex/log" |
| | | "github.com/c4milo/unpackit" |
| | | "github.com/pkg/errors" |
| | | "github.com/zan8in/gologger" |
| | | ) |
| | | |
| | | // Proxy is used to proxy a reader, for example |
| | |
| | | |
| | | // 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. |
| | | 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 { |
| | | log.Debugf("unpacking %q", path) |
| | | gologger.Debug().Msgf("unpacking %q", path) |
| | | |
| | | f, err := os.Open(path) |
| | | if err != nil { |
| | | return errors.Wrap(err, "opening tarball") |
| | | } |
| | | |
| | | err = unpackit.Unpack(f, "") |
| | | 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") |
| | | } |
| | | |
| | | bin := filepath.Join(path, m.Command) |
| | | latestBinary := filepath.Join(tempdir, m.Command) |
| | | |
| | | if err := os.Chmod(bin, 0755); err != nil { |
| | | if err := os.Chmod(latestBinary, 0755); err != nil { |
| | | return errors.Wrap(err, "chmod") |
| | | } |
| | | |
| | | dst := filepath.Join(dir, m.Command) |
| | | tmp := dst + ".tmp" |
| | | currentBinary := filepath.Join(dir, m.Command) |
| | | latestBinaryTmp := currentBinary + ".tmp" |
| | | |
| | | log.Debugf("copy %q to %q", bin, tmp) |
| | | if err := copyFile(tmp, bin); err != nil { |
| | | 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 := dst + ".old" |
| | | log.Debugf("windows workaround renaming %q to %q", dst, old) |
| | | if err := os.Rename(dst, old); err != nil { |
| | | 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") |
| | | } |
| | | } |
| | | |
| | | log.Debugf("renaming %q to %q", tmp, dst) |
| | | if err := os.Rename(tmp, dst); err != nil { |
| | | gologger.Debug().Msgf("renaming %q to %q", latestBinaryTmp, currentBinary) |
| | | if err := os.Rename(latestBinaryTmp, currentBinary); err != nil { |
| | | return errors.Wrap(err, "renaming") |
| | | } |
| | | |
| | |
| | | |
| | | // Install binary to replace the current version. |
| | | func (m *Manager) Install(path string) error { |
| | | executablePath, err := os.Executable() |
| | | // 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) |
| | | } |
| | | absolutePath, err := filepath.Abs(executablePath) |
| | | if err != nil { |
| | | return errors.Wrapf(err, "looking up path of %q", m.Command) |
| | | } |
| | | |
| | | dir := strings.TrimSuffix(absolutePath, "/"+m.Command) |
| | | |
| | | return m.InstallTo(path, dir) |
| | | } |
| | | |
| | |
| | | |
| | | // DownloadProxy the asset to a tmp directory and return its path. |
| | | func (a *Asset) DownloadProxy(proxy Proxy) (string, error) { |
| | | f, err := ioutil.TempFile(os.TempDir(), "update-") |
| | | f, err := os.CreateTemp(os.TempDir(), "update-") |
| | | if err != nil { |
| | | return "", errors.Wrap(err, "creating temp file") |
| | | } |
| | | |
| | | log.Debugf("fetch %q", a.URL) |
| | | 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")) |
| | | log.Debugf("response %s – %s (%d KiB)", res.Status, kind, size/1024) |
| | | gologger.Debug().Msgf("response %s – %s (%d KiB)", res.Status, kind, size/1024) |
| | | |
| | | body := proxy(size, res.Body) |
| | | |
| | |
| | | return "", errors.Wrap(err, res.Status) |
| | | } |
| | | |
| | | log.Debugf("copy to %q", f.Name()) |
| | | gologger.Debug().Msgf("copy to %q", f.Name()) |
| | | if _, err := io.Copy(f, body); err != nil { |
| | | body.Close() |
| | | return "", errors.Wrap(err, "copying body") |
| | |
| | | return "", errors.Wrap(err, "closing file") |
| | | } |
| | | |
| | | log.Debugf("copied") |
| | | gologger.Debug().Msgf("copied") |
| | | return f.Name(), nil |
| | | } |
| | | |
| | |
| | | |
| | | 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 |
| | | } |