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",
|
||||
"GoVersion": "go1.3",
|
||||
"Packages": [
|
||||
"./selfupdate/"
|
||||
],
|
||||
"GoVersion": "go1.4",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "bitbucket.org/kardianos/osext",
|
||||
"Comment": "null-13",
|
||||
"Rev": "5d3ddcf53a508cc2f7404eaebf546ef2cb5cdb6e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/inconshreveable/go-update",
|
||||
"Rev": "221d034a558b4c21b0624b2a450c076913854a57"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/binarydist",
|
||||
"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
|
||||
=============
|
||||
|
||||
[](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).
|
||||
|
||||
## Features
|
||||
|
Loading…
x
Reference in New Issue
Block a user