-
Notifications
You must be signed in to change notification settings - Fork 134
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
332f21b
commit 7c09b00
Showing
5 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package update | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/kardianos/osext" | ||
) | ||
|
||
type ( | ||
// ReleaseInfo represents github latest release info | ||
ReleaseInfo struct { | ||
TagName string `json:"tag_name"` | ||
Name string `json:"name"` | ||
Body string `json:"body"` | ||
Draft bool `json:"draft"` | ||
Prerelease bool `json:"prerelease"` | ||
CreatedAt time.Time `json:"created_at"` | ||
PublishedAt time.Time `json:"published_at"` | ||
Assets []Asset `json:"assets"` | ||
} | ||
Asset struct { | ||
Name string `json:"name"` | ||
Size int `json:"size"` | ||
DownloadCount int `json:"download_count"` | ||
BrowserDownloadURL string `json:"browser_download_url"` | ||
} | ||
) | ||
|
||
func (r *ReleaseInfo) getDownloadURL(name string) string { | ||
for _, a := range r.Assets { | ||
if a.Name == name { | ||
return a.BrowserDownloadURL | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
// fetchReleaseInfo return githublatest release info | ||
func fetchReleaseInfo(ctx context.Context) (*ReleaseInfo, error) { | ||
url := "https://api.github.com/repos/thedevsaddam/docgen/releases/latest" | ||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) | ||
defer cancel() | ||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return nil, errors.New("update: failed to fetch latest release") | ||
} | ||
|
||
rf := ReleaseInfo{} | ||
if err := json.NewDecoder(resp.Body).Decode(&rf); err != nil { | ||
return nil, err | ||
} | ||
return &rf, nil | ||
} | ||
|
||
// updateBinary download the binary in current binary and replace the old one | ||
func updateBinary(ctx context.Context, url string) error { | ||
if url == "" { | ||
return errors.New("update: empty download url") | ||
} | ||
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) | ||
defer cancel() | ||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | ||
if err != nil { | ||
return err | ||
} | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return errors.New("update: failed to fetch binary file") | ||
} | ||
|
||
dir, err := osext.ExecutableFolder() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fileName := filepath.Join(dir, filepath.Base(os.Args[0])) | ||
tmpFile := fileName + ".tmp" | ||
|
||
if err := os.Chmod(fileName, 0777); err != nil { | ||
return err | ||
} | ||
|
||
if err := os.Rename(fileName, tmpFile); err != nil { | ||
return err | ||
} | ||
|
||
f, err := os.Create(fileName) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := os.Chmod(fileName, 0777); err != nil { | ||
return err | ||
} | ||
|
||
_, err = io.Copy(f, resp.Body) | ||
if err != nil { | ||
if err := os.Rename(tmpFile, fileName); err != nil { | ||
return err | ||
} | ||
return err | ||
} | ||
|
||
return os.Remove(tmpFile) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package update | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"runtime" | ||
"time" | ||
|
||
"github.com/Masterminds/semver" | ||
"github.com/briandowns/spinner" | ||
) | ||
|
||
const ( | ||
layoutISO = "2006-01-02" | ||
updateInDays = 7 | ||
) | ||
|
||
// SelfUpdate update the application to its latest version | ||
// if the current release is 3days old and has a new update | ||
func SelfUpdate(ctx context.Context, buildDate, version string) error { | ||
if buildDate == "unknown" { | ||
return errors.New("update: unable to update based on unkown build date/version") | ||
} | ||
currBinaryReleaseDate, err := time.Parse(layoutISO, buildDate) | ||
if err != nil { | ||
return fmt.Errorf("update: %v", err) | ||
} | ||
if (time.Since(currBinaryReleaseDate).Hours() / 24) <= updateInDays { | ||
return nil | ||
} | ||
|
||
releaseInfo, err := fetchReleaseInfo(ctx) | ||
if err != nil { | ||
return fmt.Errorf("update: %v", err) | ||
} | ||
|
||
if releaseInfo.Draft || releaseInfo.Prerelease { | ||
return nil | ||
} | ||
|
||
c, err := semver.NewConstraint(">" + version) | ||
if err != nil { | ||
return fmt.Errorf("update: %v", err) | ||
} | ||
|
||
v, err := semver.NewVersion(releaseInfo.TagName) | ||
if err != nil { | ||
return fmt.Errorf("update: %v", err) | ||
} | ||
// Check if the version meets the constraints. The a variable will be true. | ||
if !c.Check(v) { | ||
return nil | ||
} | ||
|
||
os := runtime.GOOS | ||
arch := runtime.GOARCH | ||
fmt.Println("Found newer version:", releaseInfo.TagName) | ||
|
||
s := spinner.New(spinner.CharSets[70], 100*time.Millisecond, spinner.WithHiddenCursor(true)) // code:39 is earth for the lib | ||
s.Prefix = fmt.Sprintf("Updating from %s to %s ( ", version, releaseInfo.Name) | ||
s.Suffix = ")" | ||
s.Start() | ||
name := fmt.Sprintf("%s_%s", os, arch) | ||
if os == "windows" { | ||
name = name + ".exe" | ||
} | ||
err = updateBinary(ctx, releaseInfo.getDownloadURL(name)) | ||
s.Stop() | ||
fmt.Println() | ||
fmt.Println("Update includes:") | ||
fmt.Print(releaseInfo.Body) | ||
fmt.Println() | ||
|
||
return err | ||
} |