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