Browse Source

Merge branch 'autoupdate'

Alan Shreve 12 years ago
parent
commit
bd3356071e

+ 0 - 1
Makefile

@@ -1,5 +1,4 @@
 .PHONY: default server client deps fmt clean all release-client release-server release-all client-assets server-assets
 .PHONY: default server client deps fmt clean all release-client release-server release-all client-assets server-assets
-BUILDTAGS=
 export GOPATH:=$(shell pwd)
 export GOPATH:=$(shell pwd)
 
 
 default: all
 default: all

+ 5 - 42
src/ngrok/client/main.go

@@ -1,11 +1,9 @@
 package client
 package client
 
 
 import (
 import (
-	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"math"
 	"math"
-	"net/http"
 	"ngrok/client/ui"
 	"ngrok/client/ui"
 	"ngrok/client/views/term"
 	"ngrok/client/views/term"
 	"ngrok/client/views/web"
 	"ngrok/client/views/web"
@@ -21,11 +19,10 @@ import (
 )
 )
 
 
 const (
 const (
-	pingInterval         = 20 * time.Second
-	maxPongLatency       = 15 * time.Second
-	versionCheckInterval = 6 * time.Hour
-	versionEndpoint      = "http://dl.ngrok.com/versions"
-	BadGateway           = `<html>
+	pingInterval        = 20 * time.Second
+	maxPongLatency      = 15 * time.Second
+	updateCheckInterval = 6 * time.Hour
+	BadGateway          = `<html>
 <body style="background-color: #97a8b9">
 <body style="background-color: #97a8b9">
     <div style="margin:auto; width:400px;padding: 20px 60px; background-color: #D3D3D3; border: 5px solid maroon;">
     <div style="margin:auto; width:400px;padding: 20px 60px; background-color: #D3D3D3; border: 5px solid maroon;">
         <h2>Tunnel %s unavailable</h2>
         <h2>Tunnel %s unavailable</h2>
@@ -87,40 +84,6 @@ Content-Length: %d
 	ctl.Update(s)
 	ctl.Update(s)
 }
 }
 
 
-func versionCheck(s *State, ctl *ui.Controller) {
-	check := func() {
-		resp, err := http.Get(versionEndpoint)
-		if err != nil {
-			log.Warn("Failed to get version info %s: %v", versionEndpoint, err)
-			return
-		}
-		defer resp.Body.Close()
-
-		var payload struct {
-			Client struct {
-				Version string
-			}
-		}
-
-		err = json.NewDecoder(resp.Body).Decode(&payload)
-		if err != nil {
-			log.Warn("Failed to read version info: %v", err)
-			return
-		}
-
-		if payload.Client.Version != version.MajorMinor() {
-			s.newVersion = payload.Client.Version
-			ctl.Update(s)
-		}
-	}
-
-	// check immediately and then at a set interval
-	check()
-	for _ = range time.Tick(versionCheckInterval) {
-		check()
-	}
-}
-
 /*
 /*
  * Hearbeating to ensure our connection ngrokd is still live
  * Hearbeating to ensure our connection ngrokd is still live
  */
  */
@@ -298,7 +261,7 @@ func Main() {
 	}
 	}
 
 
 	go reconnectingControl(s, ctl)
 	go reconnectingControl(s, ctl)
-	go versionCheck(s, ctl)
+	go autoUpdate(s, ctl, opts.authtoken)
 
 
 	quitMessage := ""
 	quitMessage := ""
 	ctl.Wait.Add(1)
 	ctl.Wait.Add(1)

+ 3 - 2
src/ngrok/client/state.go

@@ -2,6 +2,7 @@ package client
 
 
 import (
 import (
 	metrics "github.com/inconshreveable/go-metrics"
 	metrics "github.com/inconshreveable/go-metrics"
+	"ngrok/client/ui"
 	"ngrok/proto"
 	"ngrok/proto"
 	"ngrok/version"
 	"ngrok/version"
 )
 )
@@ -11,7 +12,7 @@ type State struct {
 	id            string
 	id            string
 	publicUrl     string
 	publicUrl     string
 	serverVersion string
 	serverVersion string
-	newVersion    string
+	update        ui.UpdateStatus
 	protocol      proto.Protocol
 	protocol      proto.Protocol
 	opts          *Options
 	opts          *Options
 	metrics       *ClientMetrics
 	metrics       *ClientMetrics
@@ -28,7 +29,7 @@ func (s State) GetLocalAddr() string        { return s.opts.localaddr }
 func (s State) GetWebPort() int             { return s.opts.webport }
 func (s State) GetWebPort() int             { return s.opts.webport }
 func (s State) GetStatus() string           { return s.status }
 func (s State) GetStatus() string           { return s.status }
 func (s State) GetProtocol() proto.Protocol { return s.protocol }
 func (s State) GetProtocol() proto.Protocol { return s.protocol }
-func (s State) GetNewVersion() string       { return s.newVersion }
+func (s State) GetUpdate() ui.UpdateStatus  { return s.update }
 
 
 func (s State) GetConnectionMetrics() (metrics.Meter, metrics.Timer) {
 func (s State) GetConnectionMetrics() (metrics.Meter, metrics.Timer) {
 	return s.metrics.connMeter, s.metrics.connTimer
 	return s.metrics.connMeter, s.metrics.connTimer

+ 10 - 1
src/ngrok/client/ui/state.go

@@ -5,10 +5,19 @@ import (
 	"ngrok/proto"
 	"ngrok/proto"
 )
 )
 
 
+type UpdateStatus int
+
+const (
+	UpdateNone = -1 * iota
+	UpdateInstalling
+	UpdateReady
+	UpdateError
+)
+
 type State interface {
 type State interface {
 	GetClientVersion() string
 	GetClientVersion() string
 	GetServerVersion() string
 	GetServerVersion() string
-	GetNewVersion() string
+	GetUpdate() UpdateStatus
 	GetPublicUrl() string
 	GetPublicUrl() string
 	GetLocalAddr() string
 	GetLocalAddr() string
 	GetStatus() string
 	GetStatus() string

+ 11 - 0
src/ngrok/client/update_debug.go

@@ -0,0 +1,11 @@
+// +build !release,!autoupdate
+
+package client
+
+import (
+	"ngrok/client/ui"
+)
+
+// no auto-updating in debug mode
+func autoUpdate(s *State, ctl *ui.Controller, token string) {
+}

+ 96 - 0
src/ngrok/client/update_release.go

@@ -0,0 +1,96 @@
+// +build release autoupdate
+
+package client
+
+import (
+	update "github.com/inconshreveable/go-update"
+	"net/http"
+	"net/url"
+	"ngrok/client/ui"
+	"ngrok/log"
+	"ngrok/version"
+	"runtime"
+	"time"
+)
+
+const (
+	updateEndpoint = "https://dl.ngrok.com/update"
+)
+
+func autoUpdate(s *State, ctl *ui.Controller, token string) {
+	update := func() (updateSuccessful bool) {
+		params := make(url.Values)
+		params.Add("version", version.MajorMinor())
+		params.Add("os", runtime.GOOS)
+		params.Add("arch", runtime.GOARCH)
+
+		download := update.NewDownload()
+		downloadComplete := make(chan int)
+		go func() {
+			for {
+				select {
+				case progress, ok := <-download.Progress:
+					if !ok {
+						close(downloadComplete)
+						return
+					} else if progress == 100 {
+						s.update = ui.UpdateInstalling
+						ctl.Update(s)
+						close(downloadComplete)
+						return
+					} else {
+						if progress%25 == 0 {
+							log.Info("Downloading update %d%% complete", progress)
+						}
+						s.update = ui.UpdateStatus(progress)
+						ctl.Update(s)
+					}
+				}
+			}
+		}()
+
+		log.Info("Checking for update")
+		err := download.UpdateFromUrl(updateEndpoint + "?" + params.Encode())
+		<-downloadComplete
+		if err != nil {
+			log.Error("Error while updating ngrok: %v", err)
+			if download.Available {
+				s.update = ui.UpdateError
+			} else {
+				s.update = ui.UpdateNone
+			}
+
+			// record the error to ngrok.com's servers for debugging purposes
+			params.Add("error", err.Error())
+			params.Add("user", token)
+			resp, err := http.PostForm("https://dl.ngrok.com/update/error", params)
+			if err != nil {
+				log.Error("Error while reporting update error")
+			}
+			resp.Body.Close()
+		} else {
+			if download.Available {
+				log.Info("Marked update ready")
+				s.update = ui.UpdateReady
+				updateSuccessful = true
+			} else {
+				log.Info("No update available at this time")
+				s.update = ui.UpdateNone
+			}
+		}
+
+		ctl.Update(s)
+		return
+	}
+
+	// try to update immediately and then at a set interval
+	update()
+	for _ = range time.Tick(updateCheckInterval) {
+		if update() {
+			// stop trying to update if the update function is successful
+			// XXX: improve this by trying to download versions newer than
+			// the last one we downloaded
+			return
+		}
+	}
+}

+ 30 - 4
src/ngrok/client/views/term/view.go

@@ -78,10 +78,36 @@ func (v *TermView) Render() {
 	v.Printf(v.w-len(quitMsg), 0, quitMsg)
 	v.Printf(v.w-len(quitMsg), 0, quitMsg)
 
 
 	// new version message
 	// new version message
-	newVersion := v.state.GetNewVersion()
-	if newVersion != "" {
-		newVersionMsg := fmt.Sprintf("new version available at http://ngrok.com")
-		v.APrintf(termbox.ColorYellow, 30, 0, newVersionMsg)
+	updateStatus := v.state.GetUpdate()
+	var updateMsg string
+	switch updateStatus {
+	case ui.UpdateNone:
+		updateMsg = ""
+	case ui.UpdateInstalling:
+		updateMsg = "ngrok is updating"
+	case ui.UpdateReady:
+		updateMsg = "ngrok has updated: restart ngrok for the new version"
+	case ui.UpdateError:
+		updateMsg = "new version available at https://ngrok.com"
+	default:
+		pct := float64(updateStatus) / 100.0
+		const barLength = 25
+		full := int(barLength * pct)
+		bar := make([]byte, barLength+2)
+		bar[0] = '['
+		bar[barLength+1] = ']'
+		for i := 0; i < 25; i++ {
+			if i <= full {
+				bar[i+1] = '#'
+			} else {
+				bar[i+1] = ' '
+			}
+		}
+		updateMsg = "Downloading update: " + string(bar)
+	}
+
+	if updateMsg != "" {
+		v.APrintf(termbox.ColorYellow, 30, 0, updateMsg)
 	}
 	}
 
 
 	v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "ngrok")
 	v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "ngrok")

+ 1 - 1
src/ngrok/version/version.go

@@ -7,7 +7,7 @@ import (
 const (
 const (
 	Proto = "1"
 	Proto = "1"
 	Major = "0"
 	Major = "0"
-	Minor = "14"
+	Minor = "15"
 )
 )
 
 
 func MajorMinor() string {
 func MajorMinor() string {