Browse Source

modify auto-updating to use the new API and check first so the server can signal manual updates as necessary

Alan Shreve 12 years ago
parent
commit
b157045a37

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

@@ -11,7 +11,7 @@ const (
 	UpdateNone = -1 * iota
 	UpdateInstalling
 	UpdateReady
-	UpdateError
+	UpdateAvailable
 )
 
 type State interface {

+ 76 - 49
src/ngrok/client/update_release.go

@@ -15,67 +15,99 @@ import (
 
 const (
 	updateEndpoint = "https://dl.ngrok.com/update"
+	checkEndpoint = "https://dl.ngrok.com/update/check"
 )
 
-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)
-					}
+func progressWatcher(s *State, ctl *ui.Controller, progress chan int, complete chan int) {
+	for {
+		select {
+		case pct, ok := <-progress:
+			if !ok {
+				close(complete)
+				return
+			} else if pct == 100 {
+				s.update = ui.UpdateInstalling
+				ctl.Update(s)
+				close(complete)
+				return
+			} else {
+				if pct%25 == 0 {
+					log.Info("Downloading update %d%% complete", pct)
 				}
+				s.update = ui.UpdateStatus(pct)
+				ctl.Update(s)
 			}
-		}()
+		}
+	}
+}
 
+func autoUpdate(s *State, ctl *ui.Controller, token string) {
+	tryAgain := true
+
+	params := make(url.Values)
+	params.Add("version", version.MajorMinor())
+	params.Add("os", runtime.GOOS)
+	params.Add("arch", runtime.GOARCH)
+
+	updateUrl := updateEndpoint + "?" + params.Encode()
+	checkUrl := checkEndpoint + "?" + params.Encode()
+
+	update := func() (tryAgain bool) {
 		log.Info("Checking for update")
-		err := download.UpdateFromUrl(updateEndpoint + "?" + params.Encode())
+		available, err := update.NewDownload(checkUrl).Check()
+		if err != nil {
+			log.Error("Error while checking for update: %v", err)
+		}
+
+		if !available {
+			log.Info("No update available")
+			return
+		}
+
+		// stop trying after a single download attempt
+		// XXX: improve this so the we can:
+		// 1. safely update multiple times
+		// 2. only retry after a network connection failure
+		tryAgain = false
+
+		download := update.NewDownload(updateUrl)
+		downloadComplete := make(chan int)
+
+		go progressWatcher(s, ctl, download.Progress, downloadComplete)
+
+		log.Info("Trying to update . . .")
+		err, errRecover := download.GetAndUpdate()
 		<-downloadComplete
+
 		if err != nil {
+			// log error to console
 			log.Error("Error while updating ngrok: %v", err)
-			if download.Available {
-				s.update = ui.UpdateError
-			} else {
-				s.update = ui.UpdateNone
+
+			if errRecover != nil {
+				log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error())
+				params.Add("errorRecover", errRecover.Error())
 			}
 
-			// record the error to ngrok.com's servers for debugging purposes
+			// log 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)
+			resp, reportErr := http.PostForm("https://dl.ngrok.com/update/error", params)
 			if err != nil {
-				log.Error("Error while reporting update error")
+				log.Error("Error while reporting update error: %v, %v", err, reportErr)
 			}
 			resp.Body.Close()
+
+			// tell the user to update manually
+			s.update = ui.UpdateAvailable
 		} else {
-			if download.Available {
-				log.Info("Marked update ready")
-				s.update = ui.UpdateReady
-				updateSuccessful = true
+			if !download.Available {
+				// this is the way the server tells us to update manually
+				log.Info("Server wants us to update manually")
+				s.update = ui.UpdateAvailable
 			} else {
-				log.Info("No update available at this time")
-				s.update = ui.UpdateNone
+				// tell the user the update is ready
+				log.Info("Update ready!")
+				s.update = ui.UpdateReady
 			}
 		}
 
@@ -86,11 +118,6 @@ func autoUpdate(s *State, ctl *ui.Controller, token string) {
 	// 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
-		}
+		for tryAgain { update() }
 	}
 }

+ 1 - 1
src/ngrok/client/views/term/view.go

@@ -87,7 +87,7 @@ func (v *TermView) Render() {
 		updateMsg = "ngrok is updating"
 	case ui.UpdateReady:
 		updateMsg = "ngrok has updated: restart ngrok for the new version"
-	case ui.UpdateError:
+	case ui.UpdateAvailable:
 		updateMsg = "new version available at https://ngrok.com"
 	default:
 		pct := float64(updateStatus) / 100.0