github.com/grahambrereton-form3/tilt@v0.10.18/internal/hud/server/controller.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/pkg/browser"
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/windmilleng/tilt/internal/network"
    13  	"github.com/windmilleng/tilt/internal/store"
    14  	"github.com/windmilleng/tilt/pkg/assets"
    15  	"github.com/windmilleng/tilt/pkg/model"
    16  )
    17  
    18  // The amount of time to wait for a reconnection before restarting the browser
    19  // window.
    20  const reconnectDur = 2 * time.Second
    21  
    22  type HeadsUpServerController struct {
    23  	host        model.WebHost
    24  	port        model.WebPort
    25  	hudServer   *HeadsUpServer
    26  	assetServer assets.Server
    27  	webURL      model.WebURL
    28  	webLoadDone bool
    29  	initDone    bool
    30  	noBrowser   model.NoBrowser
    31  }
    32  
    33  func ProvideHeadsUpServerController(host model.WebHost, port model.WebPort, hudServer *HeadsUpServer, assetServer assets.Server, webURL model.WebURL, noBrowser model.NoBrowser) *HeadsUpServerController {
    34  	return &HeadsUpServerController{
    35  		host:        host,
    36  		port:        port,
    37  		hudServer:   hudServer,
    38  		assetServer: assetServer,
    39  		webURL:      webURL,
    40  		noBrowser:   noBrowser,
    41  	}
    42  }
    43  
    44  func (s *HeadsUpServerController) TearDown(ctx context.Context) {
    45  	s.assetServer.TearDown(ctx)
    46  }
    47  
    48  func (s *HeadsUpServerController) isWebsocketConnected() bool {
    49  	connCount := atomic.LoadInt32(&(s.hudServer.numWebsocketConns))
    50  	return connCount > 0
    51  }
    52  
    53  func (s *HeadsUpServerController) maybeOpenBrowser(st store.RStore) {
    54  	if s.webURL.Empty() || s.webLoadDone || (bool)(s.noBrowser) {
    55  		return
    56  	}
    57  
    58  	if s.isWebsocketConnected() {
    59  		// Don't auto-open the web view. It's already opened.
    60  		s.webLoadDone = true
    61  		return
    62  	}
    63  
    64  	state := st.RLockState()
    65  	tiltfileCompleted := !state.TiltfileState.LastBuild().Empty()
    66  	startTime := state.TiltStartTime
    67  	st.RUnlockState()
    68  
    69  	// Only open the webview if the Tiltfile has completed.
    70  	if tiltfileCompleted {
    71  		s.webLoadDone = true
    72  
    73  		// Make sure we wait at least `reconnectDur` before opening the browser, to
    74  		// give any open pages time to reconnect. Do this on a goroutine so we don't
    75  		// hold the lock.
    76  		go func() {
    77  			runDur := time.Since(startTime)
    78  			if runDur < reconnectDur {
    79  				time.Sleep(reconnectDur - runDur)
    80  			}
    81  
    82  			if s.isWebsocketConnected() {
    83  				return
    84  			}
    85  
    86  			// We should probably dependency-inject a browser opener.
    87  			//
    88  			// It might also make sense to wait until the asset server is ready?
    89  			_ = browser.OpenURL(s.webURL.String())
    90  		}()
    91  	}
    92  }
    93  
    94  func (s *HeadsUpServerController) OnChange(ctx context.Context, st store.RStore) {
    95  	s.maybeOpenBrowser(st)
    96  
    97  	defer func() {
    98  		s.initDone = true
    99  	}()
   100  
   101  	if s.initDone || s.port == 0 {
   102  		return
   103  	}
   104  
   105  	err := network.IsBindAddrFree(network.BindAddr(string(s.host), int(s.port)))
   106  	if err != nil {
   107  		st.Dispatch(
   108  			store.NewErrorAction(
   109  				errors.Wrapf(err, "Cannot start Tilt. Maybe another process is already running on port %d? Use --port to set a custom port", s.port)))
   110  		return
   111  	}
   112  
   113  	httpServer := &http.Server{
   114  		Addr:    network.BindAddr(string(s.host), int(s.port)),
   115  		Handler: http.DefaultServeMux,
   116  	}
   117  	http.Handle("/", s.hudServer.Router())
   118  
   119  	go func() {
   120  		<-ctx.Done()
   121  		_ = httpServer.Shutdown(context.Background())
   122  	}()
   123  
   124  	go func() {
   125  		err := s.assetServer.Serve(ctx)
   126  		if err != nil && ctx.Err() == nil {
   127  			st.Dispatch(store.NewErrorAction(err))
   128  		}
   129  	}()
   130  
   131  	go func() {
   132  		err := httpServer.ListenAndServe()
   133  		if err != nil && err != http.ErrServerClosed && ctx.Err() == nil {
   134  			st.Dispatch(store.NewErrorAction(err))
   135  		}
   136  	}()
   137  }
   138  
   139  var _ store.TearDowner = &HeadsUpServerController{}