1package command 2 3import ( 4 "archive/tar" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "path" 12 "runtime" 13 14 "github.com/kardianos/osext" 15 grc "github.com/yitsushi/github-release-check" 16 "github.com/yitsushi/go-commander" 17 18 "github.com/yitsushi/totp-cli/info" 19 "github.com/yitsushi/totp-cli/util" 20) 21 22// Update structure is the representation of the update command. 23type Update struct { 24} 25 26const ( 27 binaryChmodValue = 0755 28) 29 30// Execute is the main function. It will be called on update command. 31func (c *Update) Execute(opts *commander.CommandHelper) { 32 hasUpdate, release, _ := grc.Check(info.AppRepoOwner, info.AppName, info.AppVersion) 33 34 if !hasUpdate { 35 fmt.Printf("Your %s is up-to-date. \\o/\n", info.AppName) 36 return 37 } 38 39 var assetToDownload *grc.Asset 40 41 for _, asset := range release.Assets { 42 if asset.Name == c.buildFilename(release.TagName) { 43 assetToDownload = &asset 44 break 45 } 46 } 47 48 if assetToDownload == nil { 49 fmt.Printf("Your %s is up-to-date. \\o/\n", info.AppName) 50 return 51 } 52 53 c.downloadBinary(assetToDownload.BrowserDownloadURL) 54 55 fmt.Printf("Now you have a fresh new %s \\o/\n", info.AppName) 56} 57 58func (c *Update) buildFilename(version string) string { 59 return fmt.Sprintf("%s-%s-%s-%s.tar.gz", info.AppName, version, runtime.GOOS, runtime.GOARCH) 60} 61 62func (c *Update) downloadBinary(uri string) { 63 fmt.Println(" -> Download...") 64 response, err := http.Get(uri) 65 util.Check(err) 66 67 defer response.Body.Close() 68 69 gzipReader, _ := gzip.NewReader(response.Body) 70 defer gzipReader.Close() 71 72 fmt.Println(" -> Extract...") 73 74 tarReader := tar.NewReader(gzipReader) 75 76 _, err = tarReader.Next() 77 util.Check(err) 78 79 currentExecutable, _ := osext.Executable() 80 originalPath := path.Dir(currentExecutable) 81 82 file, err := ioutil.TempFile(originalPath, info.AppName) 83 util.Check(err) 84 85 defer file.Close() 86 87 _, err = io.Copy(file, tarReader) 88 util.Check(err) 89 90 err = file.Chmod(binaryChmodValue) 91 util.Check(err) 92 93 err = os.Rename(file.Name(), currentExecutable) 94 util.Check(err) 95} 96 97// NewUpdate creates a new Update command. 98func NewUpdate(appName string) *commander.CommandWrapper { 99 return &commander.CommandWrapper{ 100 Handler: &Update{}, 101 Help: &commander.CommandDescriptor{ 102 Name: "update", 103 ShortDescription: fmt.Sprintf("Check and update %s itself", appName), 104 LongDescription: `Check for updates. 105If there is a newer version of this application for this OS and ARCH, 106then download it and replace this application with the newer one.`, 107 }, 108 } 109} 110