mirror of
https://github.com/taigrr/go-selfupdate
synced 2025-01-18 04:33:12 -08:00
Added badges
This commit is contained in:
parent
39d900cf48
commit
1bf65c3c13
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
@ -1,19 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/sanbornm/go-selfupdate",
|
"ImportPath": "github.com/sanbornm/go-selfupdate",
|
||||||
"GoVersion": "go1.3",
|
"GoVersion": "go1.4",
|
||||||
"Packages": [
|
|
||||||
"./selfupdate/"
|
|
||||||
],
|
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
|
||||||
"ImportPath": "bitbucket.org/kardianos/osext",
|
|
||||||
"Comment": "null-13",
|
|
||||||
"Rev": "5d3ddcf53a508cc2f7404eaebf546ef2cb5cdb6e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/inconshreveable/go-update",
|
|
||||||
"Rev": "221d034a558b4c21b0624b2a450c076913854a57"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/kr/binarydist",
|
"ImportPath": "github.com/kr/binarydist",
|
||||||
"Rev": "9955b0ab8708602d411341e55fffd7e0700f86bd"
|
"Rev": "9955b0ab8708602d411341e55fffd7e0700f86bd"
|
||||||
|
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE
generated
vendored
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2012 Daniel Theophanes
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
32
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go
generated
vendored
32
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go
generated
vendored
@ -1,32 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Extensions to the standard "os" package.
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import "path/filepath"
|
|
||||||
|
|
||||||
// Executable returns an absolute path that can be used to
|
|
||||||
// re-invoke the current program.
|
|
||||||
// It may not be valid after the current program exits.
|
|
||||||
func Executable() (string, error) {
|
|
||||||
p, err := executable()
|
|
||||||
return filepath.Clean(p), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns same path as Executable, returns just the folder
|
|
||||||
// path. Excludes the executable name.
|
|
||||||
func ExecutableFolder() (string, error) {
|
|
||||||
p, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
folder, _ := filepath.Split(p)
|
|
||||||
return folder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depricated. Same as Executable().
|
|
||||||
func GetExePath() (exePath string, err error) {
|
|
||||||
return Executable()
|
|
||||||
}
|
|
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go
generated
vendored
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return syscall.Fd2path(int(f.Fd()))
|
|
||||||
}
|
|
25
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go
generated
vendored
25
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go
generated
vendored
@ -1,25 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build linux netbsd openbsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
return os.Readlink("/proc/self/exe")
|
|
||||||
case "netbsd":
|
|
||||||
return os.Readlink("/proc/curproc/exe")
|
|
||||||
case "openbsd":
|
|
||||||
return os.Readlink("/proc/curproc/file")
|
|
||||||
}
|
|
||||||
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
|
||||||
}
|
|
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go
generated
vendored
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go
generated
vendored
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin freebsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var initCwd, initCwdErr = os.Getwd()
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
var mib [4]int32
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "freebsd":
|
|
||||||
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
|
||||||
case "darwin":
|
|
||||||
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
n := uintptr(0)
|
|
||||||
// Get length.
|
|
||||||
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
|
||||||
if errNum != 0 {
|
|
||||||
return "", errNum
|
|
||||||
}
|
|
||||||
if n == 0 { // This shouldn't happen.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
buf := make([]byte, n)
|
|
||||||
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
|
||||||
if errNum != 0 {
|
|
||||||
return "", errNum
|
|
||||||
}
|
|
||||||
if n == 0 { // This shouldn't happen.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
for i, v := range buf {
|
|
||||||
if v == 0 {
|
|
||||||
buf = buf[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
execPath := string(buf)
|
|
||||||
// execPath will not be empty due to above checks.
|
|
||||||
// Try to get the absolute path if the execPath is not rooted.
|
|
||||||
if execPath[0] != '/' {
|
|
||||||
execPath, err = getAbs(execPath)
|
|
||||||
if err != nil {
|
|
||||||
return execPath, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
|
|
||||||
// actual executable.
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
|
|
||||||
return execPath, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return execPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAbs(execPath string) (string, error) {
|
|
||||||
if initCwdErr != nil {
|
|
||||||
return execPath, initCwdErr
|
|
||||||
}
|
|
||||||
// The execPath may begin with a "../" or a "./" so clean it first.
|
|
||||||
// Join the two paths, trailing and starting slashes undetermined, so use
|
|
||||||
// the generic Join function.
|
|
||||||
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
|
|
||||||
}
|
|
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go
generated
vendored
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go
generated
vendored
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin linux freebsd netbsd windows
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
oexec "os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
|
|
||||||
|
|
||||||
func TestExecPath(t *testing.T) {
|
|
||||||
ep, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecPath failed: %v", err)
|
|
||||||
}
|
|
||||||
// we want fn to be of the form "dir/prog"
|
|
||||||
dir := filepath.Dir(filepath.Dir(ep))
|
|
||||||
fn, err := filepath.Rel(dir, ep)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("filepath.Rel: %v", err)
|
|
||||||
}
|
|
||||||
cmd := &oexec.Cmd{}
|
|
||||||
// make child start with a relative program path
|
|
||||||
cmd.Dir = dir
|
|
||||||
cmd.Path = fn
|
|
||||||
// forge argv[0] for child, so that we can verify we could correctly
|
|
||||||
// get real path of the executable without influenced by argv[0].
|
|
||||||
cmd.Args = []string{"-", "-test.run=XXXX"}
|
|
||||||
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) failed: %v", err)
|
|
||||||
}
|
|
||||||
outs := string(out)
|
|
||||||
if !filepath.IsAbs(outs) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
|
||||||
}
|
|
||||||
if !sameFile(outs, ep) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFile(fn1, fn2 string) bool {
|
|
||||||
fi1, err := os.Stat(fn1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fi2, err := os.Stat(fn2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return os.SameFile(fi1, fi2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if e := os.Getenv(execPath_EnvVar); e != "" {
|
|
||||||
// first chdir to another path
|
|
||||||
dir := "/"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
dir = filepath.VolumeName(".")
|
|
||||||
}
|
|
||||||
os.Chdir(dir)
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
34
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go
generated
vendored
34
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel = syscall.MustLoadDLL("kernel32.dll")
|
|
||||||
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetModuleFileName() with hModule = NULL
|
|
||||||
func executable() (exePath string, err error) {
|
|
||||||
return getModuleFileName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getModuleFileName() (string, error) {
|
|
||||||
var n uint32
|
|
||||||
b := make([]uint16, syscall.MAX_PATH)
|
|
||||||
size := uint32(len(b))
|
|
||||||
|
|
||||||
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
|
||||||
n = uint32(r0)
|
|
||||||
if n == 0 {
|
|
||||||
return "", e1
|
|
||||||
}
|
|
||||||
return string(utf16.Decode(b[0:n])), nil
|
|
||||||
}
|
|
137
Godeps/_workspace/src/github.com/inconshreveable/go-update/README
generated
vendored
137
Godeps/_workspace/src/github.com/inconshreveable/go-update/README
generated
vendored
@ -1,137 +0,0 @@
|
|||||||
PACKAGE DOCUMENTATION
|
|
||||||
|
|
||||||
package update
|
|
||||||
import "github.com/inconshreveable/go-update"
|
|
||||||
|
|
||||||
Package update allows a program to "self-update", replacing its
|
|
||||||
executable file with new bytes.
|
|
||||||
|
|
||||||
Package update provides the facility to create user experiences like
|
|
||||||
auto-updating or user-approved updates which manifest as user prompts in
|
|
||||||
commercial applications with copy similar to "Restart to being using the
|
|
||||||
new version of X".
|
|
||||||
|
|
||||||
Updating your program to a new version is as easy as:
|
|
||||||
|
|
||||||
err := update.FromUrl("http://release.example.com/2.0/myprogram")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Update failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
The most low-level API is FromStream() which updates the current
|
|
||||||
executable with the bytes read from an io.Reader.
|
|
||||||
|
|
||||||
Additional APIs are provided for common update strategies which include
|
|
||||||
updating from a file with FromFile() and updating from the internet with
|
|
||||||
FromUrl().
|
|
||||||
|
|
||||||
Using the more advaced Download.UpdateFromUrl() API gives you the
|
|
||||||
ability to resume an interrupted download to enable large updates to
|
|
||||||
complete even over intermittent or slow connections. This API also
|
|
||||||
enables more fine-grained control over how the update is downloaded from
|
|
||||||
the internet as well as access to download progress,
|
|
||||||
|
|
||||||
|
|
||||||
VARIABLES
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Returned when the remote server indicates that no download is available
|
|
||||||
UpdateUnavailable error
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
FUNCTIONS
|
|
||||||
|
|
||||||
func FromFile(filepath string) (err error)
|
|
||||||
FromFile reads the contents of the given file and uses them to update
|
|
||||||
the current program's executable file by calling FromStream().
|
|
||||||
|
|
||||||
func FromStream(newBinary io.Reader) (err error)
|
|
||||||
FromStream reads the contents of the supplied io.Reader newBinary and
|
|
||||||
uses them to update the current program's executable file.
|
|
||||||
|
|
||||||
FromStream performs the following actions to ensure a cross-platform
|
|
||||||
safe update:
|
|
||||||
|
|
||||||
- Renames the current program's executable file from
|
|
||||||
/path/to/program-name to /path/to/.program-name.old
|
|
||||||
|
|
||||||
- Opens the now-empty path /path/to/program-name with mode 0755 and
|
|
||||||
copies the contents of newBinary into the file.
|
|
||||||
|
|
||||||
- If the copy is successful, it erases /path/to/.program.old. If this
|
|
||||||
operation fails, no error is reported.
|
|
||||||
|
|
||||||
- If the copy is unsuccessful, it attempts to rename
|
|
||||||
/path/to/.program-name.old back to /path/to/program-name. If this
|
|
||||||
operation fails, the error is not reported in order to not mask the
|
|
||||||
error that caused the rename recovery attempt.
|
|
||||||
|
|
||||||
func FromUrl(url string) error
|
|
||||||
FromUrl downloads the contents of the given url and uses them to update
|
|
||||||
the current program's executable file. It is a convenience function
|
|
||||||
which is equivalent to
|
|
||||||
|
|
||||||
NewDownload().UpdateFromUrl(url)
|
|
||||||
|
|
||||||
See Download.UpdateFromUrl for more details.
|
|
||||||
|
|
||||||
|
|
||||||
TYPES
|
|
||||||
|
|
||||||
type Download struct {
|
|
||||||
// net/http.Client to use when downloading the update.
|
|
||||||
// If nil, a default http.Client is used
|
|
||||||
HttpClient *http.Client
|
|
||||||
|
|
||||||
// Path on the file system to dowload the update to
|
|
||||||
// If empty, a temporary file is used.
|
|
||||||
// After the download begins, this path will be set
|
|
||||||
// so that the client can use it to resume aborted
|
|
||||||
// downloads
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// Progress returns the percentage of the download
|
|
||||||
// completed as an integer between 0 and 100
|
|
||||||
Progress chan (int)
|
|
||||||
|
|
||||||
// HTTP Method to use in the download request. Default is "GET"
|
|
||||||
Method string
|
|
||||||
}
|
|
||||||
Type Download encapsulates the necessary parameters and state needed to
|
|
||||||
download an update from the internet. Create an instance with the
|
|
||||||
NewDownload() factory function.
|
|
||||||
|
|
||||||
|
|
||||||
func NewDownload() *Download
|
|
||||||
NewDownload initializes a new Download object
|
|
||||||
|
|
||||||
|
|
||||||
func (d *Download) UpdateFromUrl(url string) (err error)
|
|
||||||
UpdateFromUrl downloads the given url from the internet to a file on
|
|
||||||
disk and then calls FromStream() to update the current program's
|
|
||||||
executable file with the contents of that file.
|
|
||||||
|
|
||||||
If the update is successful, the downloaded file will be erased from
|
|
||||||
disk. Otherwise, it will remain in d.Path to allow the download to
|
|
||||||
resume later or be skipped entirely.
|
|
||||||
|
|
||||||
Only HTTP/1.1 servers that implement the Range header are supported.
|
|
||||||
|
|
||||||
UpdateFromUrl() uses HTTP status codes to determine what action to take.
|
|
||||||
|
|
||||||
- The HTTP server should return 200 or 206 for the update to be
|
|
||||||
downloaded.
|
|
||||||
|
|
||||||
- The HTTP server should return 204 if no update is available at this
|
|
||||||
time. This will cause UpdateFromUrl to return the error
|
|
||||||
UpdateUnavailable.
|
|
||||||
|
|
||||||
- If the HTTP server returns a 3XX redirect, it will be followed
|
|
||||||
according to d.HttpClient's redirect policy.
|
|
||||||
|
|
||||||
- Any other HTTP status code will cause UpdateFromUrl to return an
|
|
||||||
error.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
455
Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go
generated
vendored
455
Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go
generated
vendored
@ -1,455 +0,0 @@
|
|||||||
/*
|
|
||||||
Package update allows a program to "self-update", replacing its executable file
|
|
||||||
with new bytes.
|
|
||||||
|
|
||||||
Package update provides the facility to create user experiences like auto-updating
|
|
||||||
or user-approved updates which manifest as user prompts in commercial applications
|
|
||||||
with copy similar to "Restart to being using the new version of X".
|
|
||||||
|
|
||||||
Updating your program to a new version is as easy as:
|
|
||||||
|
|
||||||
err := update.FromUrl("http://release.example.com/2.0/myprogram")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Update failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
The most low-level API is FromStream() which updates the current executable
|
|
||||||
with the bytes read from an io.Reader.
|
|
||||||
|
|
||||||
Additional APIs are provided for common update strategies which include
|
|
||||||
updating from a file with FromFile() and updating from the internet with
|
|
||||||
FromUrl().
|
|
||||||
|
|
||||||
Using the more advaced Download.UpdateFromUrl() API gives you the ability
|
|
||||||
to resume an interrupted download to enable large updates to complete even
|
|
||||||
over intermittent or slow connections. This API also enables more fine-grained
|
|
||||||
control over how the update is downloaded from the internet as well as access to
|
|
||||||
download progress,
|
|
||||||
*/
|
|
||||||
package update
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"bitbucket.org/kardianos/osext"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MeteredReader struct {
|
|
||||||
rd io.ReadCloser
|
|
||||||
totalSize int64
|
|
||||||
progress chan int
|
|
||||||
totalRead int64
|
|
||||||
ticks int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MeteredReader) Close() error {
|
|
||||||
return m.rd.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MeteredReader) Read(b []byte) (n int, err error) {
|
|
||||||
chunkSize := (m.totalSize / 100) + 1
|
|
||||||
lenB := int64(len(b))
|
|
||||||
|
|
||||||
var nChunk int
|
|
||||||
for start := int64(0); start < lenB; start += int64(nChunk) {
|
|
||||||
end := start + chunkSize
|
|
||||||
if end > lenB {
|
|
||||||
end = lenB
|
|
||||||
}
|
|
||||||
|
|
||||||
nChunk, err = m.rd.Read(b[start:end])
|
|
||||||
|
|
||||||
n += nChunk
|
|
||||||
m.totalRead += int64(nChunk)
|
|
||||||
|
|
||||||
if m.totalRead > (m.ticks * chunkSize) {
|
|
||||||
m.ticks += 1
|
|
||||||
// try to send on channel, but don't block if it's full
|
|
||||||
select {
|
|
||||||
case m.progress <- int(m.ticks + 1):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// give the progress channel consumer a chance to run
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We wrap the round tripper when making requests
|
|
||||||
// because we need to add headers to the requests we make
|
|
||||||
// even when they are requests made after a redirect
|
|
||||||
type RoundTripper struct {
|
|
||||||
RoundTripFn func(*http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
return rt.RoundTripFn(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type Download encapsulates the necessary parameters and state
|
|
||||||
// needed to download an update from the internet. Create an instance
|
|
||||||
// with the NewDownload() factory function.
|
|
||||||
//
|
|
||||||
// You may only use a Download once,
|
|
||||||
type Download struct {
|
|
||||||
// net/http.Client to use when downloading the update.
|
|
||||||
// If nil, a default http.Client is used
|
|
||||||
HttpClient *http.Client
|
|
||||||
|
|
||||||
// Path on the file system to dowload the update to
|
|
||||||
// If empty, a temporary file is used.
|
|
||||||
// After the download begins, this path will be set
|
|
||||||
// so that the client can use it to resume aborted
|
|
||||||
// downloads
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// Progress returns the percentage of the download
|
|
||||||
// completed as an integer between 0 and 100
|
|
||||||
Progress chan (int)
|
|
||||||
|
|
||||||
// HTTP Method to use in the download request. Default is "GET"
|
|
||||||
Method string
|
|
||||||
|
|
||||||
// HTTP URL to issue the download request to
|
|
||||||
Url string
|
|
||||||
|
|
||||||
// Set to true when the server confirms a new version is available
|
|
||||||
// even if the updating process encounters an error later on
|
|
||||||
Available bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDownload initializes a new Download object
|
|
||||||
func NewDownload(url string) *Download {
|
|
||||||
return &Download{
|
|
||||||
HttpClient: new(http.Client),
|
|
||||||
Progress: make(chan int),
|
|
||||||
Method: "GET",
|
|
||||||
Url: url,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
|
|
||||||
// create the download request
|
|
||||||
req, err := http.NewRequest(d.Method, d.Url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to add headers like this so they get used across redirects
|
|
||||||
trans := d.HttpClient.Transport
|
|
||||||
if trans == nil {
|
|
||||||
trans = http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
d.HttpClient.Transport = &RoundTripper{
|
|
||||||
RoundTripFn: func(r *http.Request) (*http.Response, error) {
|
|
||||||
// add header for download continuation
|
|
||||||
if offset > 0 {
|
|
||||||
r.Header.Add("Range", fmt.Sprintf("%d-", offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ask for gzipped content so that net/http won't unzip it for us
|
|
||||||
// and destroy the content length header we need for progress calculations
|
|
||||||
r.Header.Add("Accept-Encoding", "gzip")
|
|
||||||
|
|
||||||
return trans.RoundTrip(r)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// issue the download request
|
|
||||||
return d.HttpClient.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Download) Check() (available bool, err error) {
|
|
||||||
resp, err := d.sharedHttp(0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
// ok
|
|
||||||
case 200, 206:
|
|
||||||
available = true
|
|
||||||
|
|
||||||
// no update available
|
|
||||||
case 204:
|
|
||||||
available = false
|
|
||||||
|
|
||||||
// server error
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get() downloads the given url from the internet to a file on disk
|
|
||||||
// and then calls FromStream() to update the current program's executable file
|
|
||||||
// with the contents of that file.
|
|
||||||
//
|
|
||||||
// If the update is successful, the downloaded file will be erased from disk.
|
|
||||||
// Otherwise, it will remain in d.Path to allow the download to resume later
|
|
||||||
// or be skipped entirely.
|
|
||||||
//
|
|
||||||
// Only HTTP/1.1 servers that implement the Range header support resuming a
|
|
||||||
// partially completed download.
|
|
||||||
//
|
|
||||||
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
|
|
||||||
//
|
|
||||||
// - The HTTP server should return 200 or 206 for the update to be downloaded.
|
|
||||||
//
|
|
||||||
// - The HTTP server should return 204 if no update is available at this time.
|
|
||||||
//
|
|
||||||
// - If the HTTP server returns a 3XX redirect, it will be followed
|
|
||||||
// according to d.HttpClient's redirect policy.
|
|
||||||
//
|
|
||||||
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
|
|
||||||
func (d *Download) Get() (err error) {
|
|
||||||
var offset int64 = 0
|
|
||||||
var fp *os.File
|
|
||||||
|
|
||||||
// Close the progress channel whenever this function completes
|
|
||||||
defer close(d.Progress)
|
|
||||||
|
|
||||||
// open a file where we will stream the downloaded update to
|
|
||||||
// we do this first because if the caller specified a non-empty dlpath
|
|
||||||
// we need to determine how large it is in order to resume the download
|
|
||||||
if d.Path == "" {
|
|
||||||
// no dlpath specified, use a random tempfile
|
|
||||||
fp, err = ioutil.TempFile("", "update")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
// remember the path
|
|
||||||
d.Path = fp.Name()
|
|
||||||
} else {
|
|
||||||
fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
// determine the file size so we can resume the download, if possible
|
|
||||||
var fi os.FileInfo
|
|
||||||
fi, err = fp.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = fi.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// start downloading the file
|
|
||||||
resp, err := d.sharedHttp(offset)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
// ok
|
|
||||||
case 200, 206:
|
|
||||||
d.Available = true
|
|
||||||
|
|
||||||
// no update available
|
|
||||||
case 204:
|
|
||||||
return
|
|
||||||
|
|
||||||
// server error
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine how much we have to download
|
|
||||||
// net/http sets this to -1 when it is unknown
|
|
||||||
clength := resp.ContentLength
|
|
||||||
|
|
||||||
// Read the content from the response body
|
|
||||||
rd := resp.Body
|
|
||||||
|
|
||||||
// meter the rate at which we download content for
|
|
||||||
// progress reporting if we know how much to expect
|
|
||||||
if clength > 0 {
|
|
||||||
rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress the content if necessary
|
|
||||||
if resp.Header.Get("Content-Encoding") == "gzip" {
|
|
||||||
rd, err = gzip.NewReader(rd)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the update
|
|
||||||
_, err = io.Copy(fp, rd)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Download) GetAndUpdate() (err error, errRecover error) {
|
|
||||||
// check before we download if this will work
|
|
||||||
if err = SanityCheck(); err != nil {
|
|
||||||
// keep the contract that d.Progress will close whenever Get() terminates
|
|
||||||
close(d.Progress)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// download the update
|
|
||||||
if err = d.Get(); err != nil || !d.Available {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply the update
|
|
||||||
if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the temporary file
|
|
||||||
os.Remove(d.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromUrl downloads the contents of the given url and uses them to update
|
|
||||||
// the current program's executable file. It is a convenience function which is equivalent to
|
|
||||||
//
|
|
||||||
// NewDownload(url).GetAndUpdate()
|
|
||||||
//
|
|
||||||
// See Download.Get() for more details.
|
|
||||||
func FromUrl(url string) (err error, errRecover error) {
|
|
||||||
return NewDownload(url).GetAndUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromFile reads the contents of the given file and uses them
|
|
||||||
// to update the current program's executable file by calling FromStream().
|
|
||||||
func FromFile(filepath string) (err error, errRecover error) {
|
|
||||||
// open the new binary
|
|
||||||
fp, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
// do the update
|
|
||||||
return FromStream(fp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromStream reads the contents of the supplied io.Reader newBinary
|
|
||||||
// and uses them to update the current program's executable file.
|
|
||||||
//
|
|
||||||
// FromStream performs the following actions to ensure a cross-platform safe
|
|
||||||
// update:
|
|
||||||
//
|
|
||||||
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
|
|
||||||
// the contents of newBinary into the file
|
|
||||||
//
|
|
||||||
// - Renames the current program's executable file from /path/to/program-name
|
|
||||||
// to /path/to/.program-name.old
|
|
||||||
//
|
|
||||||
// - Renames /path/to/.program-name.new to /path/to/program-name
|
|
||||||
//
|
|
||||||
// - If the rename is successful, it erases /path/to/.program.old. If this operation
|
|
||||||
// fails, no error is reported.
|
|
||||||
//
|
|
||||||
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
|
|
||||||
// back to /path/to/program-name. If this operation fails, the error is not reported
|
|
||||||
// in order to not mask the error that caused the rename recovery attempt.
|
|
||||||
func FromStream(newBinary io.Reader) (err error, errRecover error) {
|
|
||||||
// get the path to the executable
|
|
||||||
thisExecPath, err := osext.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the directory the executable exists in
|
|
||||||
execDir := filepath.Dir(thisExecPath)
|
|
||||||
execName := filepath.Base(thisExecPath)
|
|
||||||
|
|
||||||
// Copy the contents of of newbinary to a the new executable file
|
|
||||||
newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
|
|
||||||
fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
_, err = io.Copy(fp, newBinary)
|
|
||||||
|
|
||||||
// if we don't call fp.Close(), windows won't let us move the new executable
|
|
||||||
// because the file will still be "in use"
|
|
||||||
fp.Close()
|
|
||||||
|
|
||||||
// this is where we'll move the executable to so that we can swap in the updated replacement
|
|
||||||
oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))
|
|
||||||
|
|
||||||
// delete any existing old exec file - this is necessary on Windows for two reasons:
|
|
||||||
// 1. after a successful update, windows can't remove the .old file because the process is still running
|
|
||||||
// 2. windows rename operations fail if the destination file already exists
|
|
||||||
_ = os.Remove(oldExecPath)
|
|
||||||
|
|
||||||
// move the existing executable to a new file in the same directory
|
|
||||||
err = os.Rename(thisExecPath, oldExecPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// move the new exectuable in to become the new program
|
|
||||||
err = os.Rename(newExecPath, thisExecPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// copy unsuccessful
|
|
||||||
errRecover = os.Rename(oldExecPath, thisExecPath)
|
|
||||||
} else {
|
|
||||||
// copy successful, remove the old binary
|
|
||||||
_ = os.Remove(oldExecPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SanityCheck() attempts to determine whether an in-place executable update could
|
|
||||||
// succeed by performing preliminary checks (to establish valid permissions, etc).
|
|
||||||
// This helps avoid downloading updates when we know the update can't be successfully
|
|
||||||
// applied later.
|
|
||||||
func SanityCheck() (err error) {
|
|
||||||
// get the path to the executable
|
|
||||||
thisExecPath, err := osext.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the directory the executable exists in
|
|
||||||
execDir := filepath.Dir(thisExecPath)
|
|
||||||
execName := filepath.Base(thisExecPath)
|
|
||||||
|
|
||||||
// attempt to open a file in the executable's directory
|
|
||||||
newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
|
|
||||||
fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fp.Close()
|
|
||||||
|
|
||||||
_ = os.Remove(newExecPath)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,6 +1,9 @@
|
|||||||
go-selfupdate
|
go-selfupdate
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/sanbornm/go-selfupdate/selfupdate)
|
||||||
|
[](https://travis-ci.org/sanbornm/go-selfupdate)
|
||||||
|
|
||||||
Enable your Golang applications to self update. Inspired by Chrome based on Heroku's [hk](https://github.com/heroku/hk).
|
Enable your Golang applications to self update. Inspired by Chrome based on Heroku's [hk](https://github.com/heroku/hk).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
Loading…
x
Reference in New Issue
Block a user