<commit_before>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/ONSdigital/log.go/log"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var getAssetETag = assets.GetAssetETag
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Namespace = "florence"
	log.Event(nil, "florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Event(nil, "error getting configuration", log.Error(err))
		os.Exit(1)
	}

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	/*
		NOTE:
		If there's any issues with this Florence server proxying redirects
		from either Babbage or Zebedee then the code in the previous Java
		Florence server might give some clues for a solution: https://github.com/ONSdigital/florence/blob/b13df0708b30493b98e9ce239103c59d7f409f98/src/main/java/com/github/onsdigital/florence/filter/Proxy.java#L125-L135

		The code has purposefully not been included in this Go replacement
		because we can't see what issue it's fixing and whether it's necessary.
	*/

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Event(nil, "error parsing router URL", log.Error(err))
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Event(nil, "error parsing zebedee URL", log.Error(err))
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Event(nil, "error parsing recipe API URL", log.Error(err))
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Event(nil, "error parsing table renderer URL", log.Error(err))
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Event(nil, "error parsing import API URL", log.Error(err))
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Event(nil, "error parsing dataset API URL", log.Error(err))
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	router := pat.New()

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Event(nil, "error creating vault client", log.Error(err))
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Event(nil, "error creating file uploader", log.Error(err))
		os.Exit(1)
	}

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	if cfg.SharedConfig.EnableDatasetImport {
		router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
		router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
		router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)
		router.Handle("/recipes{uri:.*}", recipeAPIProxy)
		router.Handle("/import{uri:.*}", importAPIProxy)
		router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
		router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	}

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.Path("/florence/publishing-queue").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/reports").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/workspace").HandlerFunc(legacyIndexFile(cfg))
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.Path("/florence{uri:.*}").HandlerFunc(refactoredIndexFile(cfg))
	router.Handle("/{uri:.*}", routerProxy)

	log.Event(nil, "starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.Middleware["RequestLogger"] = log.Middleware
	s.MiddlewareOrder = []string{"RequestID", "RequestLogger"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Event(nil, "error starting http server", log.Error(err))
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Event(nil, "shutting down gracefully")
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Event(nil, "error shutting down gracefully", log.Error(err))
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")
	assetPath := "../dist/" + path

	etag, err := getAssetETag(assetPath)

	if hdr := req.Header.Get("If-None-Match"); len(hdr) > 0 && hdr == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	b, err := getAsset(assetPath)
	if err != nil {
		log.Event(req.Context(), "error getting asset", log.Error(err))
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`ETag`, etag)
	w.Header().Set(`Cache-Control`, "no-cache")
	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting legacy html file")

		b, err := getAsset("../dist/legacy-assets/index.html")
		if err != nil {
			log.Event(req.Context(), "error getting legacy html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func refactoredIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting refactored html file")

		b, err := getAsset("../dist/refactored.html")
		if err != nil {
			log.Event(req.Context(), "error getting refactored html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Event(req.Context(), "error upgrading connection to websocket", log.Error(err))
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.Event(req.Context(), "error writing version message", log.Error(err))
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.Event(req.Context(), "error reading websocket message", log.Error(err))
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.Event(req.Context(), "error reading websocket bytes", log.Error(err), log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.Event(req.Context(), "error unmarshalling websocket message", log.Error(err), log.Data{"data": string(eventData)})
				continue
			}
			log.Event(req.Context(), "client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.Event(req.Context(), "error writing websocket ack", log.Error(err))
			}
		default:
			log.Event(req.Context(), "unknown websocket event type", log.Data{"type": eventType, "data": string(eventData)})
		}
	}
}

// FIXME this is to fix go-ns reverse proxy replacing host name so that
// babbage has correct values when using {{location.hostname}} in handlebars
func createBabbageProxy(proxyURL *url.URL, directorFunc func(*http.Request)) http.Handler {
	proxy := httputil.NewSingleHostReverseProxy(proxyURL)
	director := proxy.Director
	proxy.Transport = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   5 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}
	proxy.Director = func(req *http.Request) {
		director(req)
		//req.Host = proxyURL.Host
		if directorFunc != nil {
			directorFunc(req)
		}
	}

	return proxy
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<commit_msg>remove old comments and unused code<commit_after>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/ONSdigital/log.go/log"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var getAssetETag = assets.GetAssetETag
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Namespace = "florence"
	log.Event(nil, "florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Event(nil, "error getting configuration", log.Error(err))
		os.Exit(1)
	}

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Event(nil, "error parsing router URL", log.Error(err))
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Event(nil, "error parsing zebedee URL", log.Error(err))
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Event(nil, "error parsing recipe API URL", log.Error(err))
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Event(nil, "error parsing table renderer URL", log.Error(err))
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Event(nil, "error parsing import API URL", log.Error(err))
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Event(nil, "error parsing dataset API URL", log.Error(err))
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Event(nil, "error creating vault client", log.Error(err))
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Event(nil, "error creating file uploader", log.Error(err))
		os.Exit(1)
	}

	router := pat.New()

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	if cfg.SharedConfig.EnableDatasetImport {
		router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
		router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
		router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)
		router.Handle("/recipes{uri:.*}", recipeAPIProxy)
		router.Handle("/import{uri:.*}", importAPIProxy)
		router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
		router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	}

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.Path("/florence/publishing-queue").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/reports").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/workspace").HandlerFunc(legacyIndexFile(cfg))
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.Path("/florence{uri:.*}").HandlerFunc(refactoredIndexFile(cfg))
	router.Handle("/{uri:.*}", routerProxy)

	log.Event(nil, "starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.Middleware["RequestLogger"] = log.Middleware
	s.MiddlewareOrder = []string{"RequestID", "RequestLogger"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Event(nil, "error starting http server", log.Error(err))
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Event(nil, "shutting down gracefully")
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Event(nil, "error shutting down gracefully", log.Error(err))
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")
	assetPath := "../dist/" + path

	etag, err := getAssetETag(assetPath)

	if hdr := req.Header.Get("If-None-Match"); len(hdr) > 0 && hdr == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	b, err := getAsset(assetPath)
	if err != nil {
		log.Event(req.Context(), "error getting asset", log.Error(err))
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`ETag`, etag)
	w.Header().Set(`Cache-Control`, "no-cache")
	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting legacy html file")

		b, err := getAsset("../dist/legacy-assets/index.html")
		if err != nil {
			log.Event(req.Context(), "error getting legacy html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func refactoredIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting refactored html file")

		b, err := getAsset("../dist/refactored.html")
		if err != nil {
			log.Event(req.Context(), "error getting refactored html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Event(req.Context(), "error upgrading connection to websocket", log.Error(err))
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.Event(req.Context(), "error writing version message", log.Error(err))
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.Event(req.Context(), "error reading websocket message", log.Error(err))
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.Event(req.Context(), "error reading websocket bytes", log.Error(err), log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.Event(req.Context(), "error unmarshalling websocket message", log.Error(err), log.Data{"data": string(eventData)})
				continue
			}
			log.Event(req.Context(), "client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.Event(req.Context(), "error writing websocket ack", log.Error(err))
			}
		default:
			log.Event(req.Context(), "unknown websocket event type", log.Data{"type": eventType, "data": string(eventData)})
		}
	}
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"text/tabwriter"

	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/rackerlabs/libcarina"
)

func writeCluster(w *tabwriter.Writer, cluster *libcarina.Cluster) {
	s := strings.Join([]string{cluster.ClusterName,
		cluster.Username,
		cluster.Flavor,
		cluster.Image,
		fmt.Sprintf("%v", cluster.Nodes),
		cluster.Status}, "\t")
	w.Write([]byte(s + "\n"))
}

func writeCredentials(w *tabwriter.Writer, creds *libcarina.Credentials, pth string) (err error) {
	// TODO: Prompt when file already exists?
	for fname, b := range creds.Files {
		p := path.Join(pth, fname)
		err = ioutil.WriteFile(p, b, 0600)
		if err != nil {
			return err
		}
	}

	return nil
}

// CarinaApplication is, our, well, application
type CarinaApplication struct {
	*kingpin.Application
	TabWriter *tabwriter.Writer
}

// CarinaCommand is a command is a command needing a ClusterClient
type CarinaCommand struct {
	*kingpin.CmdClause
	ClusterClient *libcarina.ClusterClient
	TabWriter     *tabwriter.Writer
	Username      string
	APIKey        string
	Endpoint      string
}

// CarinaClusterCommand is a CarinaCommand with a ClusterName set
type CarinaClusterCommand struct {
	*CarinaCommand
	ClusterName string
}

// CarinaCredentialsCommand keeps context about the download command
type CarinaCredentialsCommand struct {
	*CarinaClusterCommand
	Path string
}

// GrowCommand keeps context about the number of nodes to scale by
type GrowCommand struct {
	*CarinaClusterCommand
	Nodes int
}

// NewCarina creates a new CarinaApplication
func NewCarina() *CarinaApplication {

	app := kingpin.New("carina", "command line interface to work with Docker Swarm clusters")

	cap := new(CarinaApplication)

	cap.Application = app

	writer := new(tabwriter.Writer)
	writer.Init(os.Stdout, 0, 8, 1, '\t', 0)

	cap.TabWriter = writer

	listCommand := cap.NewCarinaCommand(writer, "list", "list swarm clusters")
	listCommand.Action(listCommand.List)

	getCommand := cap.NewCarinaClusterCommand(writer, "get", "get information about a swarm cluster")
	getCommand.Action(getCommand.Get)

	deleteCommand := cap.NewCarinaClusterCommand(writer, "delete", "delete a swarm cluster")
	deleteCommand.Action(deleteCommand.Delete)

	createCommand := cap.NewCarinaClusterCommand(writer, "create", "create a swarm cluster")
	createCommand.Action(createCommand.Create)

	credentialsCommand := new(CarinaCredentialsCommand)
	credentialsCommand.CarinaClusterCommand = cap.NewCarinaClusterCommand(writer, "credentials", "download credentials")
	credentialsCommand.Flag("path", "path to write credentials out to").StringVar(&credentialsCommand.Path)
	credentialsCommand.Action(credentialsCommand.Download)

	growCommand := new(GrowCommand)
	growCommand.CarinaClusterCommand = cap.NewCarinaClusterCommand(writer, "grow", "Grow a cluster by the requested number of nodes")
	growCommand.Flag("nodes", "number of nodes to increase the cluster by").Required().IntVar(&growCommand.Nodes)
	growCommand.Action(growCommand.Grow)

	return cap
}

// NewCarinaCommand creates a command that relies on Auth
func (app *CarinaApplication) NewCarinaCommand(writer *tabwriter.Writer, name, help string) *CarinaCommand {
	carina := new(CarinaCommand)

	carina.CmdClause = app.Command(name, help)
	carina.Flag("username", "Rackspace username").Default("").OverrideDefaultFromEnvar("RACKSPACE_USERNAME").StringVar(&carina.Username)
	carina.Flag("api-key", "Rackspace API Key").Default("").OverrideDefaultFromEnvar("RACKSPACE_APIKEY").StringVar(&carina.APIKey)
	carina.Flag("endpoint", "Carina API endpoint").Default(libcarina.BetaEndpoint).StringVar(&carina.Endpoint)

	carina.PreAction(carina.Auth)

	carina.TabWriter = new(tabwriter.Writer)
	carina.TabWriter.Init(os.Stdout, 0, 8, 1, '\t', 0)

	return carina
}

// NewCarinaClusterCommand is a command that uses a cluster name
func (app *CarinaApplication) NewCarinaClusterCommand(writer *tabwriter.Writer, name, help string) *CarinaClusterCommand {
	cc := new(CarinaClusterCommand)
	cc.CarinaCommand = app.NewCarinaCommand(writer, name, help)
	cc.Arg("cluster-name", "name of the cluster").Required().StringVar(&cc.ClusterName)
	return cc
}

// Auth does the authentication
func (carina *CarinaCommand) Auth(pc *kingpin.ParseContext) (err error) {
	carina.ClusterClient, err = libcarina.NewClusterClient(carina.Endpoint, carina.Username, carina.APIKey)
	return err
}

// List the current swarm clusters
func (carina *CarinaCommand) List(pc *kingpin.ParseContext) (err error) {
	clusterList, err := carina.ClusterClient.List()
	if err != nil {
		return err
	}

	headerFields := []string{
		"ClusterName",
		"Username",
		"Flavor",
		"Image",
		"Nodes",
		"Status",
	}
	s := strings.Join(headerFields, "\t")

	carina.TabWriter.Write([]byte(s + "\n"))

	for _, cluster := range clusterList {
		writeCluster(carina.TabWriter, &cluster)
	}
	carina.TabWriter.Flush()

	return nil
}

// Get an individual cluster
func (carina *CarinaClusterCommand) Get(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Get(carina.ClusterName)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Delete a cluster
func (carina *CarinaClusterCommand) Delete(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Delete(carina.ClusterName)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Create a cluster
func (carina *CarinaClusterCommand) Create(pc *kingpin.ParseContext) (err error) {
	c := libcarina.Cluster{
		ClusterName: carina.ClusterName,
	}
	cluster, err := carina.ClusterClient.Create(c)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Grow increase the size of the given cluster
func (carina *GrowCommand) Grow(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Grow(carina.ClusterName, carina.Nodes)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Download credentials for a cluster
func (carina *CarinaCredentialsCommand) Download(pc *kingpin.ParseContext) (err error) {
	credentials, err := carina.ClusterClient.GetCredentials(carina.ClusterName)

	p := path.Clean(carina.Path)

	if p != "." {
		os.MkdirAll(p, 0777)
	}

	if err != nil {
		return err
	}

	writeCredentials(carina.TabWriter, credentials, p)
	// TODO: Handle Windows conditionally
	fmt.Fprintf(os.Stdout, "source \"%v\"\n", path.Join(p, "docker.env"))
	fmt.Fprintf(os.Stdout, "# Run the above or use a subshell with your arguments to %v\n", os.Args[0])
	fmt.Fprintf(os.Stdout, "# $( %v command... ) \n", os.Args[0])

	carina.TabWriter.Flush()
	return err
}

func main() {
	app := NewCarina()
	kingpin.MustParse(app.Parse(os.Args[1:]))
}
<commit_msg>Create: allow setting nodes and autoscale<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"text/tabwriter"

	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/rackerlabs/libcarina"
)

func writeCluster(w *tabwriter.Writer, cluster *libcarina.Cluster) {
	s := strings.Join([]string{cluster.ClusterName,
		cluster.Username,
		cluster.Flavor,
		cluster.Image,
		fmt.Sprintf("%v", cluster.Nodes),
		cluster.Status}, "\t")
	w.Write([]byte(s + "\n"))
}

func writeCredentials(w *tabwriter.Writer, creds *libcarina.Credentials, pth string) (err error) {
	// TODO: Prompt when file already exists?
	for fname, b := range creds.Files {
		p := path.Join(pth, fname)
		err = ioutil.WriteFile(p, b, 0600)
		if err != nil {
			return err
		}
	}

	return nil
}

// CarinaApplication is, our, well, application
type CarinaApplication struct {
	*kingpin.Application
	TabWriter *tabwriter.Writer
}

// CarinaCommand is a command is a command needing a ClusterClient
type CarinaCommand struct {
	*kingpin.CmdClause
	ClusterClient *libcarina.ClusterClient
	TabWriter     *tabwriter.Writer
	Username      string
	APIKey        string
	Endpoint      string
}

// CarinaClusterCommand is a CarinaCommand with a ClusterName set
type CarinaClusterCommand struct {
	*CarinaCommand
	ClusterName string
}

// CarinaCredentialsCommand keeps context about the download command
type CarinaCredentialsCommand struct {
	*CarinaClusterCommand
	Path string
}

// CarinaCreateCommand keeps context about the create command
type CarinaCreateCommand struct {
	*CarinaClusterCommand

	Wait bool

	// Options passed along to Carina itself
	Nodes     int
	AutoScale bool

	// TODO: See if setting flavor or image makes sense, even if the API takes it
	// Flavor    string
	// Image     string
}

// GrowCommand keeps context about the number of nodes to scale by
type GrowCommand struct {
	*CarinaClusterCommand
	Nodes int
}

// NewCarina creates a new CarinaApplication
func NewCarina() *CarinaApplication {

	app := kingpin.New("carina", "command line interface to work with Docker Swarm clusters")

	cap := new(CarinaApplication)

	cap.Application = app

	writer := new(tabwriter.Writer)
	writer.Init(os.Stdout, 0, 8, 1, '\t', 0)

	cap.TabWriter = writer

	listCommand := cap.NewCarinaCommand(writer, "list", "list swarm clusters")
	listCommand.Action(listCommand.List)

	getCommand := cap.NewCarinaClusterCommand(writer, "get", "get information about a swarm cluster")
	getCommand.Action(getCommand.Get)

	deleteCommand := cap.NewCarinaClusterCommand(writer, "delete", "delete a swarm cluster")
	deleteCommand.Action(deleteCommand.Delete)

	createCommand := new(CarinaCreateCommand)
	createCommand.CarinaClusterCommand = cap.NewCarinaClusterCommand(writer, "create", "create a swarm cluster")
	createCommand.Flag("wait", "wait for swarm cluster completion").BoolVar(&createCommand.Wait)
	createCommand.Flag("nodes", "number of nodes for the initial cluster").Default("1").IntVar(&createCommand.Nodes)
	createCommand.Flag("autoscale", "whether autoscale is on or off").BoolVar(&createCommand.AutoScale)
	createCommand.Action(createCommand.Create)

	credentialsCommand := new(CarinaCredentialsCommand)
	credentialsCommand.CarinaClusterCommand = cap.NewCarinaClusterCommand(writer, "credentials", "download credentials")
	credentialsCommand.Flag("path", "path to write credentials out to").StringVar(&credentialsCommand.Path)
	credentialsCommand.Action(credentialsCommand.Download)

	growCommand := new(GrowCommand)
	growCommand.CarinaClusterCommand = cap.NewCarinaClusterCommand(writer, "grow", "Grow a cluster by the requested number of nodes")
	growCommand.Flag("nodes", "number of nodes to increase the cluster by").Required().IntVar(&growCommand.Nodes)
	growCommand.Action(growCommand.Grow)

	return cap
}

// NewCarinaCommand creates a command that relies on Auth
func (app *CarinaApplication) NewCarinaCommand(writer *tabwriter.Writer, name, help string) *CarinaCommand {
	carina := new(CarinaCommand)

	carina.CmdClause = app.Command(name, help)
	carina.Flag("username", "Rackspace username").Default("").OverrideDefaultFromEnvar("RACKSPACE_USERNAME").StringVar(&carina.Username)
	carina.Flag("api-key", "Rackspace API Key").Default("").OverrideDefaultFromEnvar("RACKSPACE_APIKEY").StringVar(&carina.APIKey)
	carina.Flag("endpoint", "Carina API endpoint").Default(libcarina.BetaEndpoint).StringVar(&carina.Endpoint)

	carina.PreAction(carina.Auth)

	carina.TabWriter = new(tabwriter.Writer)
	carina.TabWriter.Init(os.Stdout, 0, 8, 1, '\t', 0)

	return carina
}

// NewCarinaClusterCommand is a command that uses a cluster name
func (app *CarinaApplication) NewCarinaClusterCommand(writer *tabwriter.Writer, name, help string) *CarinaClusterCommand {
	cc := new(CarinaClusterCommand)
	cc.CarinaCommand = app.NewCarinaCommand(writer, name, help)
	cc.Arg("cluster-name", "name of the cluster").Required().StringVar(&cc.ClusterName)
	return cc
}

// Auth does the authentication
func (carina *CarinaCommand) Auth(pc *kingpin.ParseContext) (err error) {
	carina.ClusterClient, err = libcarina.NewClusterClient(carina.Endpoint, carina.Username, carina.APIKey)
	return err
}

// List the current swarm clusters
func (carina *CarinaCommand) List(pc *kingpin.ParseContext) (err error) {
	clusterList, err := carina.ClusterClient.List()
	if err != nil {
		return err
	}

	headerFields := []string{
		"ClusterName",
		"Username",
		"Flavor",
		"Image",
		"Nodes",
		"Status",
	}
	s := strings.Join(headerFields, "\t")

	carina.TabWriter.Write([]byte(s + "\n"))

	for _, cluster := range clusterList {
		writeCluster(carina.TabWriter, &cluster)
	}
	carina.TabWriter.Flush()

	return nil
}

// Get an individual cluster
func (carina *CarinaClusterCommand) Get(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Get(carina.ClusterName)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Delete a cluster
func (carina *CarinaClusterCommand) Delete(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Delete(carina.ClusterName)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Create a cluster
func (carina *CarinaCreateCommand) Create(pc *kingpin.ParseContext) (err error) {
	if carina.Nodes < 1 {
		return errors.New("nodes must be >= 1")
	}

	nodes := libcarina.Number(carina.Nodes)

	c := libcarina.Cluster{
		ClusterName: carina.ClusterName,
		Nodes:       nodes,
		AutoScale:   carina.AutoScale,
	}

	cluster, err := carina.ClusterClient.Create(c)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Grow increase the size of the given cluster
func (carina *GrowCommand) Grow(pc *kingpin.ParseContext) (err error) {
	cluster, err := carina.ClusterClient.Grow(carina.ClusterName, carina.Nodes)
	if err == nil {
		writeCluster(carina.TabWriter, cluster)
	}
	carina.TabWriter.Flush()
	return err
}

// Download credentials for a cluster
func (carina *CarinaCredentialsCommand) Download(pc *kingpin.ParseContext) (err error) {
	credentials, err := carina.ClusterClient.GetCredentials(carina.ClusterName)

	p := path.Clean(carina.Path)

	if p != "." {
		os.MkdirAll(p, 0777)
	}

	if err != nil {
		return err
	}

	writeCredentials(carina.TabWriter, credentials, p)
	// TODO: Handle Windows conditionally
	fmt.Fprintf(os.Stdout, "source \"%v\"\n", path.Join(p, "docker.env"))
	fmt.Fprintf(os.Stdout, "# Run the above or use a subshell with your arguments to %v\n", os.Args[0])
	fmt.Fprintf(os.Stdout, "# $( %v command... ) \n", os.Args[0])

	carina.TabWriter.Flush()
	return err
}

func main() {
	app := NewCarina()
	kingpin.MustParse(app.Parse(os.Args[1:]))
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
)

var (
	verbose = flag.Bool("v", false, "print the names of packages as they are checked")
)

func init() {
	if err := typesInit(); err != nil {
		errExit(err)
	}
}

func main() {
	flag.Parse()
	if err := checkPaths(flag.Args(), os.Stdout); err != nil {
		errExit(err)
	}
}

func errExit(err error) {
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}

func typesMatch(wanted, got []types.Type) bool {
	if len(wanted) != len(got) {
		return false
	}
	for i, w := range wanted {
		g := got[i]
		if !types.ConvertibleTo(g, w) {
			return false
		}
	}
	return true
}

func resultsMatch(wanted, got []types.Type) bool {
	if len(got) == 0 {
		return true
	}
	return typesMatch(wanted, got)
}

func interfaceMatching(calls map[string]funcSign) string {
	matchesIface := func(decls map[string]funcSign) bool {
		if len(calls) > len(decls) {
			return false
		}
		for n, d := range decls {
			c, e := calls[n]
			if !e {
				return false
			}
			if !typesMatch(d.params, c.params) {
				return false
			}
			if !resultsMatch(d.results, c.results) {
				return false
			}
		}
		return true
	}
	for name, decls := range parsed {
		if matchesIface(decls) {
			return name
		}
	}
	return ""
}

func getDirs(d string, recursive bool) ([]string, error) {
	var dirs []string
	walkFn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			dirs = append(dirs, path)
			if !recursive {
				return filepath.SkipDir
			}
		}
		return nil
	}
	if err := filepath.Walk(d, walkFn); err != nil {
		return nil, err
	}
	return dirs, nil
}

func getPkgs(p string) ([]*build.Package, []string, error) {
	recursive := filepath.Base(p) == "..."
	if !recursive {
		info, err := os.Stat(p)
		if err != nil {
			return nil, nil, err
		}
		if !info.IsDir() {
			pkg := &build.Package{
				Name:    "stdin",
				GoFiles: []string{p},
			}
			return []*build.Package{pkg}, []string{"."}, nil
		}
	}
	d := p
	if recursive {
		d = p[:len(p)-4]
	}
	dirs, err := getDirs(d, recursive)
	if err != nil {
		return nil, nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	var pkgs []*build.Package
	var basedirs []string
	for _, d := range dirs {
		pkg, err := build.Import("./"+d, wd, 0)
		if _, ok := err.(*build.NoGoError); ok {
			continue
		}
		if err != nil {
			return nil, nil, err
		}
		pkgs = append(pkgs, pkg)
		basedirs = append(basedirs, d)
	}
	return pkgs, basedirs, nil
}

func checkPaths(paths []string, w io.Writer) error {
	conf := &types.Config{Importer: importer.Default()}
	for _, p := range paths {
		pkgs, basedirs, err := getPkgs(p)
		if err != nil {
			return err
		}
		for i, pkg := range pkgs {
			basedir := basedirs[i]
			if err := checkPkg(conf, pkg, basedir, w); err != nil {
				return err
			}
		}
	}
	return nil
}

func checkPkg(conf *types.Config, pkg *build.Package, basedir string, w io.Writer) error {
	if *verbose {
		fmt.Fprintln(w, basedir)
	}
	gp := &goPkg{
		Package: pkg,
		fset:    token.NewFileSet(),
	}
	for _, p := range pkg.GoFiles {
		fp := filepath.Join(basedir, p)
		if err := gp.parsePath(fp); err != nil {
			return err
		}
	}
	if err := gp.check(conf, w); err != nil {
		return err
	}
	return nil
}

type goPkg struct {
	*build.Package

	fset  *token.FileSet
	files []*ast.File
}

func (gp *goPkg) parsePath(fp string) error {
	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := gp.parseReader(fp, f); err != nil {
		return err
	}
	return nil
}

func (gp *goPkg) parseReader(name string, r io.Reader) error {
	f, err := parser.ParseFile(gp.fset, name, r, 0)
	if err != nil {
		return err
	}
	gp.files = append(gp.files, f)
	return nil
}

func (gp *goPkg) check(conf *types.Config, w io.Writer) error {
	info := &types.Info{
		Types: make(map[ast.Expr]types.TypeAndValue),
		Defs:  make(map[*ast.Ident]types.Object),
	}
	_, err := conf.Check(gp.Name, gp.fset, gp.files, info)
	if err != nil {
		return err
	}
	v := &Visitor{
		Info: info,
		w:    w,
		fset: gp.fset,
	}
	for _, f := range gp.files {
		ast.Walk(v, f)
	}
	return nil
}

type Visitor struct {
	*types.Info

	w    io.Writer
	fset *token.FileSet

	nodes []ast.Node

	params map[string]types.Type
	used   map[string]map[string]funcSign

	// TODO: don't just discard params with untracked usage
	unknown       map[string]struct{}
	recordUnknown bool
}

func typeMap(t *types.Tuple) map[string]types.Type {
	m := make(map[string]types.Type, t.Len())
	for i := 0; i < t.Len(); i++ {
		p := t.At(i)
		m[p.Name()] = p.Type()
	}
	return m
}

func (v *Visitor) Visit(node ast.Node) ast.Visitor {
	var top ast.Node
	if len(v.nodes) > 0 {
		top = v.nodes[len(v.nodes)-1]
	}
	switch x := node.(type) {
	case *ast.File:
	case *ast.FuncDecl:
		f := v.Defs[x.Name].(*types.Func)
		sign := f.Type().(*types.Signature)
		v.params = typeMap(sign.Params())
		v.used = make(map[string]map[string]funcSign)
		v.unknown = make(map[string]struct{})
	case *ast.CallExpr:
		if wasParamCall := v.onCall(x); wasParamCall {
			return nil
		}
	case *ast.BlockStmt:
		v.recordUnknown = true
	case *ast.Ident:
		if !v.recordUnknown {
			break
		}
		if _, e := v.params[x.Name]; e {
			v.unknown[x.Name] = struct{}{}
		}
	case nil:
		v.nodes = v.nodes[:len(v.nodes)-1]
		if _, ok := top.(*ast.FuncDecl); ok {
			v.funcEnded(top.Pos())
			v.params = nil
			v.used = nil
			v.unknown = nil
			v.recordUnknown = false
		}
	}
	if node != nil {
		v.nodes = append(v.nodes, node)
	}
	return v
}

func funcSignature(t types.Type) *types.Signature {
	switch x := t.(type) {
	case *types.Signature:
		return x
	default:
		return funcSignature(t.Underlying())
	}
}

func (v *Visitor) onCall(ce *ast.CallExpr) bool {
	if v.used == nil {
		return false
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return false
	}
	vname := left.Name
	if _, e := v.params[vname]; !e {
		return false
	}
	sign := funcSignature(v.Types[ce.Fun].Type)
	c := funcSign{}
	results := sign.Results()
	for i := 0; i < results.Len(); i++ {
		v := results.At(i)
		c.results = append(c.results, v.Type())
	}
	for _, a := range ce.Args {
		c.params = append(c.params, v.Types[a].Type)
	}
	if _, e := v.used[vname]; !e {
		v.used[vname] = make(map[string]funcSign)
	}
	fname := sel.Sel.Name
	v.used[vname][fname] = c
	return true
}

func (v *Visitor) funcEnded(pos token.Pos) {
	for name, methods := range v.used {
		if _, e := v.unknown[name]; e {
			continue
		}
		iface := interfaceMatching(methods)
		if iface == "" {
			continue
		}
		param, e := v.params[name]
		if !e {
			continue
		}
		if iface == param.String() {
			continue
		}
		pos := v.fset.Position(pos)
		fmt.Fprintf(v.w, "%s:%d: %s can be %s\n",
			pos.Filename, pos.Line, name, iface)
	}
}
<commit_msg>Skip testdata dirs<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
)

var (
	verbose = flag.Bool("v", false, "print the names of packages as they are checked")
)

func init() {
	if err := typesInit(); err != nil {
		errExit(err)
	}
}

func main() {
	flag.Parse()
	if err := checkPaths(flag.Args(), os.Stdout); err != nil {
		errExit(err)
	}
}

func errExit(err error) {
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}

func typesMatch(wanted, got []types.Type) bool {
	if len(wanted) != len(got) {
		return false
	}
	for i, w := range wanted {
		g := got[i]
		if !types.ConvertibleTo(g, w) {
			return false
		}
	}
	return true
}

func resultsMatch(wanted, got []types.Type) bool {
	if len(got) == 0 {
		return true
	}
	return typesMatch(wanted, got)
}

func interfaceMatching(calls map[string]funcSign) string {
	matchesIface := func(decls map[string]funcSign) bool {
		if len(calls) > len(decls) {
			return false
		}
		for n, d := range decls {
			c, e := calls[n]
			if !e {
				return false
			}
			if !typesMatch(d.params, c.params) {
				return false
			}
			if !resultsMatch(d.results, c.results) {
				return false
			}
		}
		return true
	}
	for name, decls := range parsed {
		if matchesIface(decls) {
			return name
		}
	}
	return ""
}

func getDirs(d string, recursive bool) ([]string, error) {
	var dirs []string
	walkFn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.Name() == "testdata" {
			return filepath.SkipDir
		}
		if info.IsDir() {
			dirs = append(dirs, path)
			if !recursive {
				return filepath.SkipDir
			}
		}
		return nil
	}
	if err := filepath.Walk(d, walkFn); err != nil {
		return nil, err
	}
	return dirs, nil
}

func getPkgs(p string) ([]*build.Package, []string, error) {
	recursive := filepath.Base(p) == "..."
	if !recursive {
		info, err := os.Stat(p)
		if err != nil {
			return nil, nil, err
		}
		if !info.IsDir() {
			pkg := &build.Package{
				Name:    "stdin",
				GoFiles: []string{p},
			}
			return []*build.Package{pkg}, []string{"."}, nil
		}
	}
	d := p
	if recursive {
		d = p[:len(p)-4]
	}
	dirs, err := getDirs(d, recursive)
	if err != nil {
		return nil, nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	var pkgs []*build.Package
	var basedirs []string
	for _, d := range dirs {
		pkg, err := build.Import("./"+d, wd, 0)
		if _, ok := err.(*build.NoGoError); ok {
			continue
		}
		if err != nil {
			return nil, nil, err
		}
		pkgs = append(pkgs, pkg)
		basedirs = append(basedirs, d)
	}
	return pkgs, basedirs, nil
}

func checkPaths(paths []string, w io.Writer) error {
	conf := &types.Config{Importer: importer.Default()}
	for _, p := range paths {
		pkgs, basedirs, err := getPkgs(p)
		if err != nil {
			return err
		}
		for i, pkg := range pkgs {
			basedir := basedirs[i]
			if err := checkPkg(conf, pkg, basedir, w); err != nil {
				return err
			}
		}
	}
	return nil
}

func checkPkg(conf *types.Config, pkg *build.Package, basedir string, w io.Writer) error {
	if *verbose {
		fmt.Fprintln(w, basedir)
	}
	gp := &goPkg{
		Package: pkg,
		fset:    token.NewFileSet(),
	}
	for _, p := range pkg.GoFiles {
		fp := filepath.Join(basedir, p)
		if err := gp.parsePath(fp); err != nil {
			return err
		}
	}
	if err := gp.check(conf, w); err != nil {
		return err
	}
	return nil
}

type goPkg struct {
	*build.Package

	fset  *token.FileSet
	files []*ast.File
}

func (gp *goPkg) parsePath(fp string) error {
	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := gp.parseReader(fp, f); err != nil {
		return err
	}
	return nil
}

func (gp *goPkg) parseReader(name string, r io.Reader) error {
	f, err := parser.ParseFile(gp.fset, name, r, 0)
	if err != nil {
		return err
	}
	gp.files = append(gp.files, f)
	return nil
}

func (gp *goPkg) check(conf *types.Config, w io.Writer) error {
	info := &types.Info{
		Types: make(map[ast.Expr]types.TypeAndValue),
		Defs:  make(map[*ast.Ident]types.Object),
	}
	_, err := conf.Check(gp.Name, gp.fset, gp.files, info)
	if err != nil {
		return err
	}
	v := &Visitor{
		Info: info,
		w:    w,
		fset: gp.fset,
	}
	for _, f := range gp.files {
		ast.Walk(v, f)
	}
	return nil
}

type Visitor struct {
	*types.Info

	w    io.Writer
	fset *token.FileSet

	nodes []ast.Node

	params map[string]types.Type
	used   map[string]map[string]funcSign

	// TODO: don't just discard params with untracked usage
	unknown       map[string]struct{}
	recordUnknown bool
}

func typeMap(t *types.Tuple) map[string]types.Type {
	m := make(map[string]types.Type, t.Len())
	for i := 0; i < t.Len(); i++ {
		p := t.At(i)
		m[p.Name()] = p.Type()
	}
	return m
}

func (v *Visitor) Visit(node ast.Node) ast.Visitor {
	var top ast.Node
	if len(v.nodes) > 0 {
		top = v.nodes[len(v.nodes)-1]
	}
	switch x := node.(type) {
	case *ast.File:
	case *ast.FuncDecl:
		f := v.Defs[x.Name].(*types.Func)
		sign := f.Type().(*types.Signature)
		v.params = typeMap(sign.Params())
		v.used = make(map[string]map[string]funcSign)
		v.unknown = make(map[string]struct{})
	case *ast.CallExpr:
		if wasParamCall := v.onCall(x); wasParamCall {
			return nil
		}
	case *ast.BlockStmt:
		v.recordUnknown = true
	case *ast.Ident:
		if !v.recordUnknown {
			break
		}
		if _, e := v.params[x.Name]; e {
			v.unknown[x.Name] = struct{}{}
		}
	case nil:
		v.nodes = v.nodes[:len(v.nodes)-1]
		if _, ok := top.(*ast.FuncDecl); ok {
			v.funcEnded(top.Pos())
			v.params = nil
			v.used = nil
			v.unknown = nil
			v.recordUnknown = false
		}
	}
	if node != nil {
		v.nodes = append(v.nodes, node)
	}
	return v
}

func funcSignature(t types.Type) *types.Signature {
	switch x := t.(type) {
	case *types.Signature:
		return x
	default:
		return funcSignature(t.Underlying())
	}
}

func (v *Visitor) onCall(ce *ast.CallExpr) bool {
	if v.used == nil {
		return false
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return false
	}
	vname := left.Name
	if _, e := v.params[vname]; !e {
		return false
	}
	sign := funcSignature(v.Types[ce.Fun].Type)
	c := funcSign{}
	results := sign.Results()
	for i := 0; i < results.Len(); i++ {
		v := results.At(i)
		c.results = append(c.results, v.Type())
	}
	for _, a := range ce.Args {
		c.params = append(c.params, v.Types[a].Type)
	}
	if _, e := v.used[vname]; !e {
		v.used[vname] = make(map[string]funcSign)
	}
	fname := sel.Sel.Name
	v.used[vname][fname] = c
	return true
}

func (v *Visitor) funcEnded(pos token.Pos) {
	for name, methods := range v.used {
		if _, e := v.unknown[name]; e {
			continue
		}
		iface := interfaceMatching(methods)
		if iface == "" {
			continue
		}
		param, e := v.params[name]
		if !e {
			continue
		}
		if iface == param.String() {
			continue
		}
		pos := v.fset.Position(pos)
		fmt.Fprintf(v.w, "%s:%d: %s can be %s\n",
			pos.Filename, pos.Line, name, iface)
	}
}
<|endoftext|>


<commit_before>package main

import "os"
import "io"
import "net"
import "log"
import "sync"
import "flag"
import "encoding/json"

var LISTEN string
var SERVER string
var USERS []string

func handleConnection(c net.Conn){

    // rfc1929: username/password

    var ver byte // 0x05 ?
    var ml,nl,pl int
    var support,allow bool
    var s net.Conn
    var err error
	var wg sync.WaitGroup

    buf := make([]byte,1500)

    defer c.Close()

    // rfc1928
    if _,err := io.ReadFull(c,buf[0:2]); err != nil {
        goto cerr
    }
    ver = buf[0]
    ml  = int(buf[1])
    if _,err := io.ReadFull(c,buf[0:ml]); err != nil {
        goto cerr
    }

    // client support username/password auth ?
    support = false
    for ;ml>=0; {
        if buf[ml] == 0x02 {
            support = true
            break
        }
        ml = ml -1
    }

    if support == false {
        log.Println("client not support rfc1929.")
        c.Write([]byte{ver,0xff})
        return
    }

    c.Write([]byte{ver,0x02})

    // rfc1929 start

    // wait user/passww
    if _,err:=io.ReadFull(c,buf[0:2]);err != nil {
        goto cerr
    }

    nl = int(buf[1])
    if _,err:=io.ReadFull(c,buf[0:nl]); err != nil {
        goto cerr
    }

    if _,err := io.ReadFull(c,buf[nl:nl+1]); err != nil {
        goto cerr
    }

    pl = int(buf[nl])

    if _,err := io.ReadFull(c,buf[nl+1:nl+pl+1]); err != nil {
        goto cerr
    }

    buf[nl] = ':';

    // buf[ username : password ]
    //               ^(nl)    ^(nl+pl+1)

    allow = false

    for _,up := range USERS {

        if up == string(buf[0:nl+pl+1]) {
            allow = true
            break
        }
    }

    if allow {
        c.Write([]byte{ver,0x00}) // success
    }else{
        c.Write([]byte{ver,0x01}) // failed
    }

    // connect to server
    s, err = net.Dial("tcp", SERVER)
    if err != nil {
        // handle error
        log.Println("connect to server failed.")
        return
    }
    defer s.Close()

    s.Write([]byte{ver,0x01,0x00}) // no auth

    if _,err := io.ReadFull(s,buf[0:2]);err != nil {
        log.Println("server connection error.")
        return
    }

    if buf[1] != 0x00 {
        log.Println("server need auth.")

        // tell client auth failed here?
        return
    }

    // loop read/write
    wg.Add(1)
    go func(){
        io.Copy(c,s)
        wg.Done()
    }()
    wg.Add(1)
    go func(){
        io.Copy(s,c)
        wg.Done()
    }()

    wg.Wait()
    return

cerr:
    log.Println("client connection error.")
    return

}

func main(){

    var pconf_file = flag.String("c","auth.json","config file format:json")

    flag.Parse()

    //log.Printf("%#v",*pconf_file)

    if cf,err := os.Open(*pconf_file); err != nil {
        log.Fatal("config read failed.")
    }else{
        defer cf.Close()

        buf := make([]byte,4096)
        n,_ := cf.Read(buf)

        //log.Printf("%s",buf[0:n])
	    var v map[string]interface{}
	    if err := json.Unmarshal(buf[0:n], &v); err != nil {
		    log.Fatal("config parse failed, %s", err)
        }

        //log.Printf("%#v",v)

        if vx,ok := v["listen"].(string); !ok {
            log.Fatal("no listen address configed.")
        }else{
            LISTEN = vx
        }

        if vx,ok := v["server"].(string); !ok {
            log.Fatal("no server address configed.")
        }else{
            SERVER = vx
        }

        if _,ok := v["users"].([]interface{}); !ok {
            log.Fatal("no users configed.")
        }

        for _,vx := range v["users"].([]interface{}) {
            if _v,ok := vx.(string); ok {
                USERS = append(USERS,_v)
            }
        }

        //log.Printf("%#v",USERS)

        if len(USERS) < 1 {
            log.Fatal("no users configed.")
        }

        cf.Close()
    }

    ln, err := net.Listen("tcp", LISTEN)
    if err != nil {
        // handle error
        log.Fatal("tcp listen failed.")
    }
    defer ln.Close()

    for {
        conn, err := ln.Accept()
        if err != nil {
            // handle error
            continue
        }
        go handleConnection(conn)
    }
}<commit_msg>gofmt<commit_after>package main

import "os"
import "io"
import "net"
import "log"
import "sync"
import "flag"
import "encoding/json"

var LISTEN string
var SERVER string
var USERS []string

func handleConnection(c net.Conn) {

	// rfc1929: username/password

	var ver byte // 0x05 ?
	var ml, nl, pl int
	var support, allow bool
	var s net.Conn
	var err error
	var wg sync.WaitGroup

	buf := make([]byte, 1500)

	defer c.Close()

	// rfc1928
	if _, err := io.ReadFull(c, buf[0:2]); err != nil {
		goto cerr
	}
	ver = buf[0]
	ml = int(buf[1])
	if _, err := io.ReadFull(c, buf[0:ml]); err != nil {
		goto cerr
	}

	// client support username/password auth ?
	support = false
	for ml >= 0 {
		if buf[ml] == 0x02 {
			support = true
			break
		}
		ml = ml - 1
	}

	if support == false {
		log.Println("client not support rfc1929.")
		c.Write([]byte{ver, 0xff})
		return
	}

	c.Write([]byte{ver, 0x02})

	// rfc1929 start

	// wait user/passww
	if _, err := io.ReadFull(c, buf[0:2]); err != nil {
		goto cerr
	}

	nl = int(buf[1])
	if _, err := io.ReadFull(c, buf[0:nl]); err != nil {
		goto cerr
	}

	if _, err := io.ReadFull(c, buf[nl:nl+1]); err != nil {
		goto cerr
	}

	pl = int(buf[nl])

	if _, err := io.ReadFull(c, buf[nl+1:nl+pl+1]); err != nil {
		goto cerr
	}

	buf[nl] = ':'

	// buf[ username : password ]
	//               ^(nl)    ^(nl+pl+1)

	allow = false

	for _, up := range USERS {

		if up == string(buf[0:nl+pl+1]) {
			allow = true
			break
		}
	}

	if allow {
		c.Write([]byte{ver, 0x00}) // success
	} else {
		c.Write([]byte{ver, 0x01}) // failed
	}

	// connect to server
	s, err = net.Dial("tcp", SERVER)
	if err != nil {
		// handle error
		log.Println("connect to server failed.")
		return
	}
	defer s.Close()

	s.Write([]byte{ver, 0x01, 0x00}) // no auth

	if _, err := io.ReadFull(s, buf[0:2]); err != nil {
		log.Println("server connection error.")
		return
	}

	if buf[1] != 0x00 {
		log.Println("server need auth.")

		// tell client auth failed here?
		return
	}

	// loop read/write
	wg.Add(1)
	go func() {
		io.Copy(c, s)
		wg.Done()
	}()
	wg.Add(1)
	go func() {
		io.Copy(s, c)
		wg.Done()
	}()

	wg.Wait()
	return

cerr:
	log.Println("client connection error.")
	return

}

func main() {

	var pconf_file = flag.String("c", "auth.json", "config file format:json")

	flag.Parse()

	//log.Printf("%#v",*pconf_file)

	if cf, err := os.Open(*pconf_file); err != nil {
		log.Fatal("config read failed.")
	} else {
		defer cf.Close()

		buf := make([]byte, 4096)
		n, _ := cf.Read(buf)

		//log.Printf("%s",buf[0:n])
		var v map[string]interface{}
		if err := json.Unmarshal(buf[0:n], &v); err != nil {
			log.Fatal("config parse failed, %s", err)
		}

		//log.Printf("%#v",v)

		if vx, ok := v["listen"].(string); !ok {
			log.Fatal("no listen address configed.")
		} else {
			LISTEN = vx
		}

		if vx, ok := v["server"].(string); !ok {
			log.Fatal("no server address configed.")
		} else {
			SERVER = vx
		}

		if _, ok := v["users"].([]interface{}); !ok {
			log.Fatal("no users configed.")
		}

		for _, vx := range v["users"].([]interface{}) {
			if _v, ok := vx.(string); ok {
				USERS = append(USERS, _v)
			}
		}

		//log.Printf("%#v",USERS)

		if len(USERS) < 1 {
			log.Fatal("no users configed.")
		}

		cf.Close()
	}

	ln, err := net.Listen("tcp", LISTEN)
	if err != nil {
		// handle error
		log.Fatal("tcp listen failed.")
	}
	defer ln.Close()

	for {
		conn, err := ln.Accept()
		if err != nil {
			// handle error
			continue
		}
		go handleConnection(conn)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	// "time"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float32 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float32 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										client.Del(history_json.Device_id[i])
										txtmessage="1:取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="2:取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="Sorry! No this device ID, please check again."
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

<commit_msg>check error<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	// "time"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float32 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float32 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										err=client.Del(history_json.Device_id[i])
										if err!=nil{
											txtmessage=err.Error()
										}
										txtmessage="1:取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="2:取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="Sorry! No this device ID, please check again."
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

<|endoftext|>


<commit_before>package main

import (
    "bytes"
    "compress/zlib"
    "encoding/binary"
    "flag"
    "fmt"
    "io"
    "io/ioutil"
    "os"

    "./dfversions"
)

func readUInt32(f *os.File) (ret uint32, err error) {
    err = binary.Read(f, binary.LittleEndian, &ret)
    return
}

func bytesRemaining (f *os.File) (ret uint64) {
    orig, _ := f.Seek(0, 1)
    end, _ := f.Seek(0, 2)
    ret = uint64(end - orig)
    f.Seek(orig, 0)
    return
}

func main() {
    var quiet bool
    flag.BoolVar(&quiet, "q", false, "Display only error messages")
    flag.Parse()
    if len(os.Args) == 1 {
        fmt.Printf("%s: No save file(s) given\n", os.Args[0])
        return
    }
    for _, file := range flag.Args() {
        f, err := os.Open(file)
        if err != nil {
            fmt.Printf("%s: Not found\n", file)
            continue
        }

        save_version, err := readUInt32(f)
        if err != nil {
            fmt.Println(err)
            continue
        }
        save_desc := dfversions.Describe(save_version)
        if !quiet {
            if dfversions.IsKnown(save_version) {
                fmt.Printf("%s: Save version %s (%d)\n", file, save_desc, save_version)
            } else {
                fmt.Printf("%s: Unknown save version: %s (%d)\n", file, save_desc, save_version)
            }
        }

        compressed, err := readUInt32(f)
        if err != nil {
            fmt.Println(err)
            continue
        }
        if compressed != 1 {
            fmt.Printf("%s: Not compressed\n", file)
            continue
        }

        // Attempt to decompress
        ok := false
        chunk := 1
        for ;; chunk++ {
            length, err := readUInt32(f)
            if err == io.EOF {
                ok = true
                break
            }
            if err != nil {
                fmt.Printf("%s: chunk %d: %s\n", file, chunk, err)
                break
            }
            mbytes := length / (1024 * 1024)
            if mbytes > 10 {
                fmt.Printf("%s: chunk %d: Memory threshold exceeded: tried to read %d MB\n", file, chunk, mbytes)
                break
            }

            buf := make([]byte, length)
            n, err := f.Read(buf)
            if err != nil {
                fmt.Printf("%s: chunk %d: %s\n", file, chunk, err)
                break
            }
            if n != int(length) {
                fmt.Printf("%s: chunk %d: Expected %d bytes, got %d\n", file, chunk, length, n)
                break
            }

            reader, err := zlib.NewReader(bytes.NewReader(buf))
            if err != nil {
                fmt.Printf("%s: chunk %d: Read failed: %s\n", file, chunk, err)
                break
            }
            _, err = io.Copy(ioutil.Discard, reader)
            if err != nil {
                fmt.Printf("%s: chunk %d: Compression error: %s\n", file, chunk, err)
                break
            }
        }
        if ok {
            if !quiet {
                fmt.Printf("%s: No compression errors detected (%d chunks)\n", file, chunk)
            }
        } else {
            fmt.Printf("%s: %d bytes unread\n", file, bytesRemaining(f))
        }
    }
}
<commit_msg>Add chunk length to errors when available<commit_after>package main

import (
    "bytes"
    "compress/zlib"
    "encoding/binary"
    "flag"
    "fmt"
    "io"
    "io/ioutil"
    "os"

    "./dfversions"
)

func readUInt32(f *os.File) (ret uint32, err error) {
    err = binary.Read(f, binary.LittleEndian, &ret)
    return
}

func bytesRemaining (f *os.File) (ret uint64) {
    orig, _ := f.Seek(0, 1)
    end, _ := f.Seek(0, 2)
    ret = uint64(end - orig)
    f.Seek(orig, 0)
    return
}

func main() {
    var quiet bool
    flag.BoolVar(&quiet, "q", false, "Display only error messages")
    flag.Parse()
    if len(os.Args) == 1 {
        fmt.Printf("%s: No save file(s) given\n", os.Args[0])
        return
    }
    for _, file := range flag.Args() {
        f, err := os.Open(file)
        if err != nil {
            fmt.Printf("%s: Not found\n", file)
            continue
        }

        save_version, err := readUInt32(f)
        if err != nil {
            fmt.Println(err)
            continue
        }
        save_desc := dfversions.Describe(save_version)
        if !quiet {
            if dfversions.IsKnown(save_version) {
                fmt.Printf("%s: Save version %s (%d)\n", file, save_desc, save_version)
            } else {
                fmt.Printf("%s: Unknown save version: %s (%d)\n", file, save_desc, save_version)
            }
        }

        compressed, err := readUInt32(f)
        if err != nil {
            fmt.Println(err)
            continue
        }
        if compressed != 1 {
            fmt.Printf("%s: Not compressed\n", file)
            continue
        }

        // Attempt to decompress
        ok := false
        chunk := 1
        for ;; chunk++ {
            length, err := readUInt32(f)
            if err == io.EOF {
                ok = true
                break
            }
            if err != nil {
                fmt.Printf("%s: chunk %d: %s\n", file, chunk, err)
                break
            }
            mbytes := length / (1024 * 1024)
            if mbytes > 10 {
                fmt.Printf("%s: chunk %d: Memory threshold exceeded: tried to read %d MB\n", file, chunk, mbytes)
                break
            }

            buf := make([]byte, length)
            n, err := f.Read(buf)
            if err != nil {
                fmt.Printf("%s: chunk %d: %s\n", file, chunk, err)
                break
            }
            if n != int(length) {
                fmt.Printf("%s: chunk %d: Expected %d bytes, got %d\n", file, chunk, length, n)
                break
            }

            reader, err := zlib.NewReader(bytes.NewReader(buf))
            if err != nil {
                fmt.Printf("%s: chunk %d (length=%d): Read failed: %s\n", file, chunk, n, err)
                break
            }
            _, err = io.Copy(ioutil.Discard, reader)
            if err != nil {
                fmt.Printf("%s: chunk %d (length=%d): Compression error: %s\n", file, chunk, n, err)
                break
            }
        }
        if ok {
            if !quiet {
                fmt.Printf("%s: No compression errors detected (%d chunks)\n", file, chunk)
            }
        } else {
            fmt.Printf("%s: %d bytes unread\n", file, bytesRemaining(f))
        }
    }
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"log"
	"os"
	"time"

	"github.com/fraenkel/candiedyaml"
	"github.com/garyburd/redigo/redis"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
	"github.com/tedsuo/router"
	proleroutes "github.com/winston-ci/prole/routes"

	"github.com/winston-ci/winston/api"
	"github.com/winston-ci/winston/api/drainer"
	apiroutes "github.com/winston-ci/winston/api/routes"
	"github.com/winston-ci/winston/builder"
	"github.com/winston-ci/winston/config"
	"github.com/winston-ci/winston/db"
	"github.com/winston-ci/winston/server"
	"github.com/winston-ci/winston/watcher"
	"github.com/winston-ci/winston/watchman"
)

var configPath = flag.String(
	"config",
	"",
	"path to winston server config .yml",
)

var templatesDir = flag.String(
	"templates",
	"",
	"path to directory containing the html templates",
)

var publicDir = flag.String(
	"public",
	"",
	"path to directory containing public resources (javascript, css, etc.)",
)

var proleURL = flag.String(
	"proleURL",
	"http://127.0.0.1:4637",
	"address denoting the prole service",
)

var redisAddr = flag.String(
	"redisAddr",
	"127.0.0.1:6379",
	"redis server address",
)

var peerAddr = flag.String(
	"peerAddr",
	"127.0.0.1:8081",
	"external address of the api server, used for callbacks",
)

var listenAddr = flag.String(
	"listenAddr",
	":8080",
	"port for the web server to listen on",
)

var apiListenAddr = flag.String(
	"apiListenAddr",
	":8081",
	"port for the api to listen on",
)

func main() {
	flag.Parse()

	if *configPath == "" {
		fatal(errors.New("must specify -config"))
	}

	if *templatesDir == "" {
		fatal(errors.New("must specify -templates"))
	}

	if *publicDir == "" {
		fatal(errors.New("must specify -public"))
	}

	configFile, err := os.Open(*configPath)
	if err != nil {
		fatal(err)
	}

	var conf config.Config
	err = candiedyaml.NewDecoder(configFile).Decode(&conf)
	if err != nil {
		fatal(err)
	}

	configFile.Close()

	redisDB := db.NewRedis(redis.NewPool(func() (redis.Conn, error) {
		return redis.DialTimeout("tcp", *redisAddr, 5*time.Second, 0, 0)
	}, 20))

	winstonEndpoint := router.NewRequestGenerator("http://"+*peerAddr, apiroutes.Routes)
	proleEndpoint := router.NewRequestGenerator(*proleURL, proleroutes.Routes)
	builder := builder.NewBuilder(redisDB, conf.Resources, proleEndpoint, winstonEndpoint)

	serverHandler, err := server.New(conf, redisDB, *templatesDir, *publicDir, *peerAddr, builder)
	if err != nil {
		fatal(err)
	}

	drainer := drainer.NewDrainer()

	apiHandler, err := api.New(redisDB, drainer)
	if err != nil {
		fatal(err)
	}

	watchman := watchman.NewWatchman(builder)

	group := grouper.EnvokeGroup(grouper.RunGroup{
		"web":     http_server.New(*listenAddr, serverHandler),
		"api":     http_server.New(*apiListenAddr, apiHandler),
		"watcher": watcher.NewWatcher(conf.Jobs, conf.Resources, redisDB, proleEndpoint, watchman),
		"drainer": ifrit.RunFunc(func(signals <-chan os.Signal, ready chan<- struct{}) error {
			close(ready)
			<-signals
			drainer.Drain()
			return nil
		}),
	})

	running := ifrit.Envoke(sigmon.New(group))

	log.Println("serving web on", *listenAddr)
	log.Println("serving api on", *apiListenAddr)

	err = <-running.Wait()
	if err != nil {
		log.Println("exited with error:", err)
		os.Exit(1)
	}

	log.Println("exited")
}

func fatal(err error) {
	println(err.Error())
	os.Exit(1)
}
<commit_msg>slight cleanup<commit_after>package main

import (
	"errors"
	"flag"
	"log"
	"os"
	"time"

	"github.com/fraenkel/candiedyaml"
	"github.com/garyburd/redigo/redis"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
	"github.com/tedsuo/router"
	proleroutes "github.com/winston-ci/prole/routes"

	"github.com/winston-ci/winston/api"
	"github.com/winston-ci/winston/api/drainer"
	apiroutes "github.com/winston-ci/winston/api/routes"
	"github.com/winston-ci/winston/builder"
	"github.com/winston-ci/winston/config"
	"github.com/winston-ci/winston/db"
	"github.com/winston-ci/winston/server"
	"github.com/winston-ci/winston/watcher"
	"github.com/winston-ci/winston/watchman"
)

var configPath = flag.String(
	"config",
	"",
	"path to winston server config .yml",
)

var templatesDir = flag.String(
	"templates",
	"",
	"path to directory containing the html templates",
)

var publicDir = flag.String(
	"public",
	"",
	"path to directory containing public resources (javascript, css, etc.)",
)

var proleURL = flag.String(
	"proleURL",
	"http://127.0.0.1:4637",
	"address denoting the prole service",
)

var redisAddr = flag.String(
	"redisAddr",
	"127.0.0.1:6379",
	"redis server address",
)

var peerAddr = flag.String(
	"peerAddr",
	"127.0.0.1:8081",
	"external address of the api server, used for callbacks",
)

var listenAddr = flag.String(
	"listenAddr",
	":8080",
	"port for the web server to listen on",
)

var apiListenAddr = flag.String(
	"apiListenAddr",
	":8081",
	"port for the api to listen on",
)

func main() {
	flag.Parse()

	if *configPath == "" {
		fatal(errors.New("must specify -config"))
	}

	if *templatesDir == "" {
		fatal(errors.New("must specify -templates"))
	}

	if *publicDir == "" {
		fatal(errors.New("must specify -public"))
	}

	configFile, err := os.Open(*configPath)
	if err != nil {
		fatal(err)
	}

	var conf config.Config
	err = candiedyaml.NewDecoder(configFile).Decode(&conf)
	if err != nil {
		fatal(err)
	}

	configFile.Close()

	redisDB := db.NewRedis(redis.NewPool(func() (redis.Conn, error) {
		return redis.DialTimeout("tcp", *redisAddr, 5*time.Second, 0, 0)
	}, 20))

	winstonEndpoint := router.NewRequestGenerator("http://"+*peerAddr, apiroutes.Routes)
	proleEndpoint := router.NewRequestGenerator(*proleURL, proleroutes.Routes)
	builder := builder.NewBuilder(redisDB, conf.Resources, proleEndpoint, winstonEndpoint)

	serverHandler, err := server.New(conf, redisDB, *templatesDir, *publicDir, *peerAddr, builder)
	if err != nil {
		fatal(err)
	}

	drainer := drainer.NewDrainer()

	apiHandler, err := api.New(redisDB, drainer)
	if err != nil {
		fatal(err)
	}

	watchman := watchman.NewWatchman(builder)

	group := grouper.EnvokeGroup(grouper.RunGroup{
		"web":     http_server.New(*listenAddr, serverHandler),
		"api":     http_server.New(*apiListenAddr, apiHandler),
		"watcher": watcher.NewWatcher(conf.Jobs, conf.Resources, redisDB, proleEndpoint, watchman),
		"drainer": ifrit.RunFunc(func(signals <-chan os.Signal, ready chan<- struct{}) error {
			close(ready)
			<-signals
			drainer.Drain()
			return nil
		}),
	})

	running := ifrit.Envoke(sigmon.New(group))

	log.Println("serving web on", *listenAddr)
	log.Println("serving api on", *apiListenAddr)

	err = <-running.Wait()
	if err != nil {
		fatal(err)
	}

	log.Println("exited")
}

func fatal(err error) {
	println(err.Error())
	os.Exit(1)
}
<|endoftext|>