<commit_before>package main

import (
	"net/http"
	"strings"
	"time"

	"facette/backend"
	"facette/connector"
	"facette/plot"
	"facette/timerange"

	"github.com/facette/httputil"
	"github.com/facette/sqlstorage"
	"github.com/hashicorp/go-uuid"
)

const (
	defaultTimeRange = "-1h"
)

type plotQuery struct {
	query     plot.Query
	queryMap  [][2]int
	connector connector.Connector
}

func (w *httpWorker) httpHandlePlots(rw http.ResponseWriter, r *http.Request) {
	var err error

	defer r.Body.Close()

	// Get plot request from received data
	req := &plot.Request{}
	if err := httputil.BindJSON(r, req); err == httputil.ErrInvalidContentType {
		httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusUnsupportedMediaType)
		return
	} else if err != nil {
		w.log.Error("unable to unmarshal JSON data: %s", err)
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
		return
	}

	// Request item from backend
	if req.ID != "" {
		req.Graph = w.service.backend.NewGraph()

		// Check for aliased item if identifier value isn't valid
		column := "id"
		if _, err := uuid.ParseUUID(req.ID); err != nil {
			column = "alias"
		}

		if err := w.service.backend.Storage().Get(column, req.ID, req.Graph, false); err == sqlstorage.ErrItemNotFound {
			httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusNotFound)
			return
		} else if err != nil {
			w.log.Error("failed to fetch item: %s", err)
			httputil.WriteJSON(rw, httpBuildMessage(ErrUnhandledError), http.StatusInternalServerError)
			return
		}
	} else if req.Graph != nil {
		// Register back-end (needed for graph expansion)
		req.Graph.Item.SetBackend(w.service.backend)
	} else {
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
		return
	}

	// Expand graph template if linked
	if err := req.Graph.Expand(req.Attributes); err != nil {
		w.log.Warning("%s", err)
		httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusInternalServerError)
		return
	}

	// Set request time boundaries and range
	// * both start and end time must be provided, or none
	// * range can't be specified if start and end are
	if req.StartTime.IsZero() && req.EndTime.IsZero() {
		if req.Time.IsZero() {
			req.Time = time.Now().UTC()
		}

		if req.Range == "" {
			if value, ok := req.Graph.Options["range"].(string); ok {
				req.Range = value
			} else {
				req.Range = defaultTimeRange
			}
		}

		if strings.HasPrefix(req.Range, "-") {
			req.EndTime = req.Time
			if req.StartTime, err = timerange.Apply(req.Time, req.Range); err != nil {
				w.log.Warning("unable to apply time range: %s", err)
				httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
				return
			}
		} else {
			req.StartTime = req.Time
			if req.EndTime, err = timerange.Apply(req.Time, req.Range); err != nil {
				w.log.Warning("unable to apply time range: %s", err)
				httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
				return
			}
		}
	} else if (req.StartTime.IsZero() || req.EndTime.IsZero()) || req.Range != "" {
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidTimerange), http.StatusBadRequest)
		return
	}

	// Set default plot sample if none provided
	if req.Sample == 0 {
		req.Sample = plot.DefaultSample
	}

	// Execute plots request
	plots := plot.Response{
		Start:   req.StartTime.Format(time.RFC3339),
		End:     req.EndTime.Format(time.RFC3339),
		Series:  w.executeRequest(req),
		Options: req.Graph.Options,
	}

	// Set fallback title to graph name if none provided
	if plots.Options == nil {
		plots.Options = make(map[string]interface{})
	}

	if _, ok := plots.Options["title"]; !ok {
		plots.Options["title"] = req.Graph.Name
	}

	httputil.WriteJSON(rw, plots, http.StatusOK)
}

func (w *httpWorker) executeRequest(req *plot.Request) []plot.SeriesResponse {
	// Expand groups series
	for _, group := range req.Graph.Groups {
		expandedSeries := []*backend.Series{}
		for _, series := range group.Series {
			expandedSeries = append(expandedSeries, w.expandSeries(series, true)...)
		}
		group.Series = expandedSeries
	}

	// Dispatch plot queries among providers
	data := make([][]plot.Series, len(req.Graph.Groups))
	for i, group := range req.Graph.Groups {
		data[i] = make([]plot.Series, len(group.Series))
	}

	for _, q := range w.dispatchQueries(req) {
		series, err := q.connector.Plots(&q.query)
		if err != nil {
			w.log.Error("unable to fetch plots: %s", err)
			continue
		}

		count := len(series)
		expected := len(q.query.Series)
		if count != expected {
			w.log.Error("unable to fetch plots: expected %d series but got %d", expected, count)
			continue
		}

		// Put back series to its original indexes
		for i, s := range series {
			data[q.queryMap[i][0]][q.queryMap[i][1]] = s
		}
	}

	// Generate plots series
	result := []plot.SeriesResponse{}
	for i, group := range req.Graph.Groups {
		var (
			consolidate int
			interpolate bool
			err         error
		)

		// Skip processing if no data
		if len(data[i]) == 0 {
			goto finalize
		}

		// Apply series scale if any
		for j, series := range group.Series {
			if v, ok := series.Options["scale"].(float64); ok {
				data[i][j].Scale(plot.Value(v))
			}
		}

		// Skip operations if none requested
		if group.Operator == plot.OperatorNone {
			goto finalize
		}

		// Get group consolidation mode and interpolation options
		consolidate = plot.ConsolidateAverage
		if v, ok := group.Options["consolidate"].(int); ok {
			consolidate = v
		}

		interpolate = true
		if v, ok := group.Options["interpolate"].(bool); ok {
			interpolate = v
		}

		// Normalize series and apply operations
		data[i], err = plot.Normalize(data[i], req.StartTime, req.EndTime, req.Sample, consolidate, interpolate)
		if err != nil {
			w.log.Error("failed to normalize series: %s", err)
			continue
		}

		switch group.Operator {
		case plot.OperatorAverage, plot.OperatorSum:
			var (
				series plot.Series
				err    error
			)

			if group.Operator == plot.OperatorAverage {
				series, err = plot.Average(data[i])
			} else {
				series, err = plot.Sum(data[i])
			}

			if err != nil {
				w.log.Error("failed to apply series operation: %s", err)
				continue
			}

			// Set series name to group name
			group.Series[0].Name = group.Name

			// Replace group series with operation result
			data[i] = []plot.Series{series}

		case plot.OperatorNormalize:
			// noop

		default:
			w.log.Warning("unknown %d operation type", group.Operator)
			continue
		}

	finalize:
		// Get group scale value
		scale, _ := group.Options["scale"].(float64)

		for j, series := range data[i] {
			// Apply group scale if any
			if scale != 0 {
				series.Scale(plot.Value(scale))
			}

			// Summarize series
			percentiles := []float64{}
			if slice, ok := req.Graph.Options["percentiles"].([]interface{}); ok {
				for _, entry := range slice {
					if val, ok := entry.(float64); ok {
						percentiles = append(percentiles, val)
					}
				}
			}

			series.Summarize(percentiles)

			result = append(result, plot.SeriesResponse{
				Series:  series,
				Name:    group.Series[j].Name,
				Options: group.Series[j].Options,
			})
		}
	}

	return result
}

func (w *httpWorker) dispatchQueries(req *plot.Request) []plotQuery {
	providers := make(map[string]*plotQuery)

	for i, group := range req.Graph.Groups {
		for j, series := range group.Series {
			if !series.IsValid() {
				w.log.Warning("invalid series metric: %s", series)
				continue
			}

			search := w.service.searcher.Metrics(series.Origin, series.Source, series.Metric, 1)
			if len(search) == 0 {
				w.log.Warning("unable to find series metric: %s", series)
				continue
			}

			// Get series connector and provider name
			c := search[0].Connector().(connector.Connector)
			provName := c.Name()

			// Initialize provider-specific plot query
			if _, ok := providers[provName]; !ok {
				providers[provName] = &plotQuery{
					query: plot.Query{
						StartTime: req.StartTime,
						EndTime:   req.EndTime,
						Sample:    req.Sample,
						Series:    []plot.QuerySeries{},
					},
					queryMap:  [][2]int{},
					connector: c,
				}
			}

			// Append new series to plot query and save series index
			providers[provName].query.Series = append(providers[provName].query.Series, plot.QuerySeries{
				Origin: search[0].Source().Origin().OriginalName,
				Source: search[0].Source().OriginalName,
				Metric: search[0].OriginalName,
			})

			providers[provName].queryMap = append(providers[provName].queryMap, [2]int{i, j})
		}
	}

	result := []plotQuery{}
	for _, q := range providers {
		result = append(result, *q)
	}

	return result
}
<commit_msg>Handle template error nicely when rendering plots<commit_after>package main

import (
	"net/http"
	"strings"
	"time"

	"facette/backend"
	"facette/connector"
	"facette/plot"
	"facette/template"
	"facette/timerange"

	"github.com/facette/httputil"
	"github.com/facette/sqlstorage"
	"github.com/hashicorp/go-uuid"
)

const (
	defaultTimeRange = "-1h"
)

type plotQuery struct {
	query     plot.Query
	queryMap  [][2]int
	connector connector.Connector
}

func (w *httpWorker) httpHandlePlots(rw http.ResponseWriter, r *http.Request) {
	var err error

	defer r.Body.Close()

	// Get plot request from received data
	req := &plot.Request{}
	if err := httputil.BindJSON(r, req); err == httputil.ErrInvalidContentType {
		httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusUnsupportedMediaType)
		return
	} else if err != nil {
		w.log.Error("unable to unmarshal JSON data: %s", err)
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
		return
	}

	// Request item from backend
	if req.ID != "" {
		req.Graph = w.service.backend.NewGraph()

		// Check for aliased item if identifier value isn't valid
		column := "id"
		if _, err := uuid.ParseUUID(req.ID); err != nil {
			column = "alias"
		}

		if err := w.service.backend.Storage().Get(column, req.ID, req.Graph, false); err == sqlstorage.ErrItemNotFound {
			httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusNotFound)
			return
		} else if err != nil {
			w.log.Error("failed to fetch item: %s", err)
			httputil.WriteJSON(rw, httpBuildMessage(ErrUnhandledError), http.StatusInternalServerError)
			return
		}
	} else if req.Graph != nil {
		// Register back-end (needed for graph expansion)
		req.Graph.Item.SetBackend(w.service.backend)
	} else {
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
		return
	}

	// Expand graph template if linked
	if err := req.Graph.Expand(req.Attributes); req.ID == "" && err == template.ErrInvalidTemplate {
		httputil.WriteJSON(rw, httpBuildMessage(err), http.StatusBadRequest)
		return
	} else if err != nil {
		w.log.Error("%s", err)
		httputil.WriteJSON(rw, httpBuildMessage(ErrUnhandledError), http.StatusInternalServerError)
		return
	}

	// Set request time boundaries and range
	// * both start and end time must be provided, or none
	// * range can't be specified if start and end are
	if req.StartTime.IsZero() && req.EndTime.IsZero() {
		if req.Time.IsZero() {
			req.Time = time.Now().UTC()
		}

		if req.Range == "" {
			if value, ok := req.Graph.Options["range"].(string); ok {
				req.Range = value
			} else {
				req.Range = defaultTimeRange
			}
		}

		if strings.HasPrefix(req.Range, "-") {
			req.EndTime = req.Time
			if req.StartTime, err = timerange.Apply(req.Time, req.Range); err != nil {
				w.log.Warning("unable to apply time range: %s", err)
				httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
				return
			}
		} else {
			req.StartTime = req.Time
			if req.EndTime, err = timerange.Apply(req.Time, req.Range); err != nil {
				w.log.Warning("unable to apply time range: %s", err)
				httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidParameter), http.StatusBadRequest)
				return
			}
		}
	} else if (req.StartTime.IsZero() || req.EndTime.IsZero()) || req.Range != "" {
		httputil.WriteJSON(rw, httpBuildMessage(ErrInvalidTimerange), http.StatusBadRequest)
		return
	}

	// Set default plot sample if none provided
	if req.Sample == 0 {
		req.Sample = plot.DefaultSample
	}

	// Execute plots request
	plots := plot.Response{
		Start:   req.StartTime.Format(time.RFC3339),
		End:     req.EndTime.Format(time.RFC3339),
		Series:  w.executeRequest(req),
		Options: req.Graph.Options,
	}

	// Set fallback title to graph name if none provided
	if plots.Options == nil {
		plots.Options = make(map[string]interface{})
	}

	if _, ok := plots.Options["title"]; !ok {
		plots.Options["title"] = req.Graph.Name
	}

	httputil.WriteJSON(rw, plots, http.StatusOK)
}

func (w *httpWorker) executeRequest(req *plot.Request) []plot.SeriesResponse {
	// Expand groups series
	for _, group := range req.Graph.Groups {
		expandedSeries := []*backend.Series{}
		for _, series := range group.Series {
			expandedSeries = append(expandedSeries, w.expandSeries(series, true)...)
		}
		group.Series = expandedSeries
	}

	// Dispatch plot queries among providers
	data := make([][]plot.Series, len(req.Graph.Groups))
	for i, group := range req.Graph.Groups {
		data[i] = make([]plot.Series, len(group.Series))
	}

	for _, q := range w.dispatchQueries(req) {
		series, err := q.connector.Plots(&q.query)
		if err != nil {
			w.log.Error("unable to fetch plots: %s", err)
			continue
		}

		count := len(series)
		expected := len(q.query.Series)
		if count != expected {
			w.log.Error("unable to fetch plots: expected %d series but got %d", expected, count)
			continue
		}

		// Put back series to its original indexes
		for i, s := range series {
			data[q.queryMap[i][0]][q.queryMap[i][1]] = s
		}
	}

	// Generate plots series
	result := []plot.SeriesResponse{}
	for i, group := range req.Graph.Groups {
		var (
			consolidate int
			interpolate bool
			err         error
		)

		// Skip processing if no data
		if len(data[i]) == 0 {
			goto finalize
		}

		// Apply series scale if any
		for j, series := range group.Series {
			if v, ok := series.Options["scale"].(float64); ok {
				data[i][j].Scale(plot.Value(v))
			}
		}

		// Skip operations if none requested
		if group.Operator == plot.OperatorNone {
			goto finalize
		}

		// Get group consolidation mode and interpolation options
		consolidate = plot.ConsolidateAverage
		if v, ok := group.Options["consolidate"].(int); ok {
			consolidate = v
		}

		interpolate = true
		if v, ok := group.Options["interpolate"].(bool); ok {
			interpolate = v
		}

		// Normalize series and apply operations
		data[i], err = plot.Normalize(data[i], req.StartTime, req.EndTime, req.Sample, consolidate, interpolate)
		if err != nil {
			w.log.Error("failed to normalize series: %s", err)
			continue
		}

		switch group.Operator {
		case plot.OperatorAverage, plot.OperatorSum:
			var (
				series plot.Series
				err    error
			)

			if group.Operator == plot.OperatorAverage {
				series, err = plot.Average(data[i])
			} else {
				series, err = plot.Sum(data[i])
			}

			if err != nil {
				w.log.Error("failed to apply series operation: %s", err)
				continue
			}

			// Set series name to group name
			group.Series[0].Name = group.Name

			// Replace group series with operation result
			data[i] = []plot.Series{series}

		case plot.OperatorNormalize:
			// noop

		default:
			w.log.Warning("unknown %d operation type", group.Operator)
			continue
		}

	finalize:
		// Get group scale value
		scale, _ := group.Options["scale"].(float64)

		for j, series := range data[i] {
			// Apply group scale if any
			if scale != 0 {
				series.Scale(plot.Value(scale))
			}

			// Summarize series
			percentiles := []float64{}
			if slice, ok := req.Graph.Options["percentiles"].([]interface{}); ok {
				for _, entry := range slice {
					if val, ok := entry.(float64); ok {
						percentiles = append(percentiles, val)
					}
				}
			}

			series.Summarize(percentiles)

			result = append(result, plot.SeriesResponse{
				Series:  series,
				Name:    group.Series[j].Name,
				Options: group.Series[j].Options,
			})
		}
	}

	return result
}

func (w *httpWorker) dispatchQueries(req *plot.Request) []plotQuery {
	providers := make(map[string]*plotQuery)

	for i, group := range req.Graph.Groups {
		for j, series := range group.Series {
			if !series.IsValid() {
				w.log.Warning("invalid series metric: %s", series)
				continue
			}

			search := w.service.searcher.Metrics(series.Origin, series.Source, series.Metric, 1)
			if len(search) == 0 {
				w.log.Warning("unable to find series metric: %s", series)
				continue
			}

			// Get series connector and provider name
			c := search[0].Connector().(connector.Connector)
			provName := c.Name()

			// Initialize provider-specific plot query
			if _, ok := providers[provName]; !ok {
				providers[provName] = &plotQuery{
					query: plot.Query{
						StartTime: req.StartTime,
						EndTime:   req.EndTime,
						Sample:    req.Sample,
						Series:    []plot.QuerySeries{},
					},
					queryMap:  [][2]int{},
					connector: c,
				}
			}

			// Append new series to plot query and save series index
			providers[provName].query.Series = append(providers[provName].query.Series, plot.QuerySeries{
				Origin: search[0].Source().Origin().OriginalName,
				Source: search[0].Source().OriginalName,
				Metric: search[0].OriginalName,
			})

			providers[provName].queryMap = append(providers[provName].queryMap, [2]int{i, j})
		}
	}

	result := []plotQuery{}
	for _, q := range providers {
		result = append(result, *q)
	}

	return result
}
<|endoftext|>


<commit_before>package services

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

type stream struct {
	CreatedAt time.Time `json:"created_at"`
	Channel   channelInfo
}

type channelInfo struct {
	Status string
	Game   string
}

type responseBody struct {
	Stream stream
}

// CheckStreamStatus runs gettting all data from all channels bot applied to
func CheckStreamStatus() {
	for _, value := range repos.Config.Channels {
		go getStatus(value)
	}
}

func getStatus(channel string) {
	var responseBody = new(responseBody)
	url := "https://api.twitch.tv/kraken/streams/" + channel
	resp, respError := http.Get(url)
	var status = models.StreamStatus{
		Online: false}
	if respError != nil {
		return
	}
	defer resp.Body.Close()

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

	if marshallError != nil {
		return
	}
	if !responseBody.Stream.CreatedAt.IsZero() {
		status = models.StreamStatus{
			Online:      true,
			Description: "я кот",
			Game:        responseBody.Stream.Channel.Game,
			Title:       responseBody.Stream.Channel.Status,
			Start:       responseBody.Stream.CreatedAt}
	}
	// else {
	// 	repos.SetStreamStatusOffline(channel)
	// }
	repos.PushStreamStatus(channel, status)

}

// func getOfflineStatus(channel string) {
// 	var responseBody = new(channelInfo)
// 	url := "https://api.twitch.tv/kraken/channels/" + channel
// 	resp, respError := http.Get(url)

// 	if respError != nil {
// 		return
// 	}
// 	defer resp.Body.Close()

// 	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

// 	if marshallError != nil {
// 		return
// 	}

// 	status := models.StreamStatus{
// 		Online: false,
// 		Game:   responseBody.Game,
// 		Title:  responseBody.Status}
// 	repos.PushStreamStatus(channel, status)

// }
<commit_msg>asfas<commit_after>package services

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

type stream struct {
	CreatedAt time.Time `json:"created_at"`
	Channel   channelInfo
}

type channelInfo struct {
	Status string
	Game   string
}

type responseBody struct {
	Stream stream
}

// CheckStreamStatus runs gettting all data from all channels bot applied to
func CheckStreamStatus() {
	for _, value := range repos.Config.Channels {
		go getStatus(value)
	}
}

func getStatus(channel string) {
	var responseBody = new(responseBody)
	url := "https://api.twitch.tv/kraken/streams/" + channel + "?client_id=c5w8oif66lg711pfrh86h8od3sek43d"
	resp, respError := http.Get(url)
	var status = models.StreamStatus{
		Online: false}
	if respError != nil {
		return
	}
	defer resp.Body.Close()

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

	if marshallError != nil {
		return
	}
	if !responseBody.Stream.CreatedAt.IsZero() {
		status = models.StreamStatus{
			Online:      true,
			Description: "я кот",
			Game:        responseBody.Stream.Channel.Game,
			Title:       responseBody.Stream.Channel.Status,
			Start:       responseBody.Stream.CreatedAt}
	}
	// else {
	// 	repos.SetStreamStatusOffline(channel)
	// }
	repos.PushStreamStatus(channel, status)

}

// func getOfflineStatus(channel string) {
// 	var responseBody = new(channelInfo)
// 	url := "https://api.twitch.tv/kraken/channels/" + channel
// 	resp, respError := http.Get(url)

// 	if respError != nil {
// 		return
// 	}
// 	defer resp.Body.Close()

// 	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

// 	if marshallError != nil {
// 		return
// 	}

// 	status := models.StreamStatus{
// 		Online: false,
// 		Game:   responseBody.Game,
// 		Title:  responseBody.Status}
// 	repos.PushStreamStatus(channel, status)

// }
<|endoftext|>


<commit_before>package core

import (
    "crypto/sha1"
    "encoding/base64"
    "errors"
    "fmt"
    "image"
    "image/jpeg"
    "image/png"
    "io"
    "net/http"
    "os"
    "os/exec"
    "path/filepath"
    "sync"

    "github.com/nfnt/resize"
)

func Optimize(originalUrl string, imageParameters ImageParameters, pathtemp string, pathmedia string) (string, string, error) {

    // Download file
    response, err := http.Get(originalUrl)
    if err != nil {
        return "", "", errors.New("Error downloading file " + originalUrl)
    }

    defer response.Body.Close()

    // Detect image type, size and last modified
    responseType := response.Header.Get("Content-Type")
    size := response.Header.Get("Content-Length")
    lastModified := response.Header.Get("Last-Modified")

    // Get Hash Name
    hash := sha1.New()
    hash.Write([]byte(fmt.Sprint(imageParameters.Width, imageParameters.Height, imageParameters.Quality, originalUrl, responseType, size, lastModified)))
    newFileName := base64.URLEncoding.EncodeToString(hash.Sum(nil))

    newImageTempPath := filepath.Join(pathtemp, newFileName)
    newImageRealPath := filepath.Join(pathmedia, newFileName)

    // Check if file exists
    if _, err := os.Stat(newImageRealPath); err == nil {
        return newImageRealPath, responseType, nil
    }

    // Decode and resize
    var reader io.Reader = response.Body
    var newFileImg *os.File
    var mu = &sync.Mutex{}

    mu.Lock()
    if _, err := os.Stat(newImageTempPath); err == nil {
        return "", "", errors.New("Still elaborating")
    }

    newFileImg, err = os.Create(newImageTempPath)
    mu.Unlock()

    var img image.Image

    if responseType == "image/jpeg" {
        img, err = jpeg.Decode(reader)
    } else if responseType == "image/png" {
        img, err = png.Decode(reader)
    } else {
        os.Remove(newImageTempPath)
        return "", "", errors.New("Format not supported")
    }

    if err != nil {
        os.Remove(newImageTempPath)
        return "", "", errors.New("Error decoding response")
    }

    newImage := resize.Resize(imageParameters.Width, imageParameters.Height, img, resize.NearestNeighbor)

    if err != nil {
        os.Remove(newImageTempPath)
        return "", "", errors.New("Error creating new image")
    }

    // Encode new image
    if responseType == "image/jpeg" {
        err = jpeg.Encode(newFileImg, newImage, nil)
        if err != nil {
            os.Remove(newImageTempPath)
            return "", "", errors.New("Error encoding response")
        }
    } else if responseType == "image/png" {
        err = png.Encode(newFileImg, newImage)
        if err != nil {
            os.Remove(newImageTempPath)
            return "", "", errors.New("Error encoding response")
        }
    }
    newFileImg.Close()

    if responseType == "image/jpeg" {
        args := []string{fmt.Sprintf("--max=%d", imageParameters.Quality), newImageTempPath}
        cmd := exec.Command("jpegoptim", args...)
        err := cmd.Run()
        if err != nil {
            os.Remove(newImageTempPath)
            return "", "", errors.New("Jpegoptim command not working")
        }
    } else if responseType == "image/png" {
        args := []string{newImageTempPath, "-f", "--ext=\"\""}
        if imageParameters.Quality != 100 {
            var qualityMin = imageParameters.Quality - 10
            qualityParameter := fmt.Sprintf("--quality=%[1]d-%[2]d", qualityMin, imageParameters.Quality)
            args = append([]string{qualityParameter}, args...)
        }
        cmd := exec.Command("pngquant", args...)
        err := cmd.Run()
        if err != nil {
            os.Remove(newImageTempPath)
            return "", "", errors.New("Pngquant command not working")
        }
    }

    err = os.Rename(newImageTempPath, newImageRealPath)
    if err != nil {
        os.Remove(newImageTempPath)
        return "", "", errors.New("Error moving file")
    }

    return newImageRealPath, responseType, nil
}

func BuildResponse(w http.ResponseWriter, imagePath string, contentType string) error {
    img, err := os.Open(imagePath)
    if err != nil {
        return errors.New("Error reading from optimized file")
    }
    defer img.Close()
    w.Header().Set("Content-Type", contentType) // <-- set the content-type header
    io.Copy(w, img)
    return nil
}
<commit_msg>modify indent via vim-go<commit_after>package core

import (
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"sync"

	"github.com/nfnt/resize"
)

func Optimize(originalUrl string, imageParameters ImageParameters, pathtemp string, pathmedia string) (string, string, error) {

	// Download file
	response, err := http.Get(originalUrl)
	if err != nil {
		return "", "", errors.New("Error downloading file " + originalUrl)
	}

	defer response.Body.Close()

	// Detect image type, size and last modified
	responseType := response.Header.Get("Content-Type")
	size := response.Header.Get("Content-Length")
	lastModified := response.Header.Get("Last-Modified")

	// Get Hash Name
	hash := sha1.New()
	hash.Write([]byte(fmt.Sprint(imageParameters.Width, imageParameters.Height, imageParameters.Quality, originalUrl, responseType, size, lastModified)))
	newFileName := base64.URLEncoding.EncodeToString(hash.Sum(nil))

	newImageTempPath := filepath.Join(pathtemp, newFileName)
	newImageRealPath := filepath.Join(pathmedia, newFileName)

	// Check if file exists
	if _, err := os.Stat(newImageRealPath); err == nil {
		return newImageRealPath, responseType, nil
	}

	// Decode and resize
	var reader io.Reader = response.Body
	var newFileImg *os.File
	var mu = &sync.Mutex{}

	mu.Lock()
	if _, err := os.Stat(newImageTempPath); err == nil {
		return "", "", errors.New("Still elaborating")
	}

	newFileImg, err = os.Create(newImageTempPath)
	mu.Unlock()

	var img image.Image

	if responseType == "image/jpeg" {
		img, err = jpeg.Decode(reader)
	} else if responseType == "image/png" {
		img, err = png.Decode(reader)
	} else {
		os.Remove(newImageTempPath)
		return "", "", errors.New("Format not supported")
	}

	if err != nil {
		os.Remove(newImageTempPath)
		return "", "", errors.New("Error decoding response")
	}

	newImage := resize.Resize(imageParameters.Width, imageParameters.Height, img, resize.NearestNeighbor)

	if err != nil {
		os.Remove(newImageTempPath)
		return "", "", errors.New("Error creating new image")
	}

	// Encode new image
	if responseType == "image/jpeg" {
		err = jpeg.Encode(newFileImg, newImage, nil)
		if err != nil {
			os.Remove(newImageTempPath)
			return "", "", errors.New("Error encoding response")
		}
	} else if responseType == "image/png" {
		err = png.Encode(newFileImg, newImage)
		if err != nil {
			os.Remove(newImageTempPath)
			return "", "", errors.New("Error encoding response")
		}
	}
	newFileImg.Close()

	if responseType == "image/jpeg" {
		args := []string{fmt.Sprintf("--max=%d", imageParameters.Quality), newImageTempPath}
		cmd := exec.Command("jpegoptim", args...)
		err := cmd.Run()
		if err != nil {
			os.Remove(newImageTempPath)
			return "", "", errors.New("Jpegoptim command not working")
		}
	} else if responseType == "image/png" {
		args := []string{newImageTempPath, "-f", "--ext=\"\""}
		if imageParameters.Quality != 100 {
			var qualityMin = imageParameters.Quality - 10
			qualityParameter := fmt.Sprintf("--quality=%[1]d-%[2]d", qualityMin, imageParameters.Quality)
			args = append([]string{qualityParameter}, args...)
		}
		cmd := exec.Command("pngquant", args...)
		err := cmd.Run()
		if err != nil {
			os.Remove(newImageTempPath)
			return "", "", errors.New("Pngquant command not working")
		}
	}

	err = os.Rename(newImageTempPath, newImageRealPath)
	if err != nil {
		os.Remove(newImageTempPath)
		return "", "", errors.New("Error moving file")
	}

	return newImageRealPath, responseType, nil
}

func BuildResponse(w http.ResponseWriter, imagePath string, contentType string) error {
	img, err := os.Open(imagePath)
	if err != nil {
		return errors.New("Error reading from optimized file")
	}
	defer img.Close()
	w.Header().Set("Content-Type", contentType) // <-- set the content-type header
	io.Copy(w, img)
	return nil
}
<|endoftext|>


<commit_before>package marathon

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/QubitProducts/bamboo/configuration"
)

// Describes an app process running
type Task struct {
	Frontend string
	Server   string
	Host     string
	Port     int
	Ports    []int
	Version  string
	Weight   int
}

// A health check on the application
type HealthCheck struct {
	// One of TCP, HTTP or COMMAND
	Protocol string
	// The path (if Protocol is HTTP)
	Path string
	// The position of the port targeted in the ports array
	PortIndex int
}

type Endpoint struct {
	Protocol string
	Bind     int
}

// An app may have multiple processes
type App struct {
	Id              string
	Frontend        string
	HealthCheckPath string
	HealthChecks    []HealthCheck
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
	Labels          map[string]string
	Endpoints       []Endpoint
	CurVsn          string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonTaskList []marathonTask

type marathonTasks struct {
	Tasks marathonTaskList `json:"tasks"`
}

type marathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice marathonTaskList) Len() int {
	return len(slice)
}

func (slice marathonTaskList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice marathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonApps struct {
	Apps []marathonApp `json:"apps"`
}

type marathonApp struct {
	Id           string                `json:"id"`
	HealthChecks []marathonHealthCheck `json:"healthChecks"`
	Ports        []int                 `json:"ports"`
	Env          map[string]string     `json:"env"`
	Labels       map[string]string     `json:"labels"`
}

type marathonHealthCheck struct {
	Path      string `json:"path"`
	Protocol  string `json:"protocol"`
	PortIndex int    `json:"portIndex"`
}

func fetchMarathonApps(endpoint string, conf *configuration.Configuration) (map[string]marathonApp, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/apps", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	var appResponse marathonApps

	contents, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &appResponse)
	if err != nil {
		return nil, err
	}

	dataById := map[string]marathonApp{}

	for _, appConfig := range appResponse.Apps {
		dataById[appConfig.Id] = appConfig
	}

	return dataById, nil
}

func fetchTasks(endpoint string, conf *configuration.Configuration) (map[string]marathonTaskList, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	var tasks marathonTasks

	if err != nil {
		return nil, err
	}

	contents, err := ioutil.ReadAll(response.Body)
	defer response.Body.Close()
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &tasks)
	if err != nil {
		return nil, err
	}

	taskList := tasks.Tasks
	sort.Sort(taskList)

	tasksById := map[string]marathonTaskList{}
	for _, task := range taskList {
		if tasksById[task.AppId] == nil {
			tasksById[task.AppId] = marathonTaskList{}
		}
		tasksById[task.AppId] = append(tasksById[task.AppId], task)
	}

	for _, task_list := range tasksById {
		sort.Sort(task_list)
	}

	return tasksById, nil
}

func createApps(tasksById map[string]marathonTaskList, marathonApps map[string]marathonApp) AppList {
	appMap := map[string]*App{}
	for _, mApp := range marathonApps {
		mappJson, _ := json.Marshal(mApp)
		log.Println("mapp", string(mappJson))
		appPath := formPath(mApp)
		log.Println("appPath", string(appPath))
		app, ok := appMap[appPath]
		if !ok {
			newApp := formApp(mApp, appPath)

			if endpointStr, ok := mApp.Env["BB_DM_ENDPOINTS"]; ok {
				endpoints := formEndpoints(endpointStr)
				newApp.Endpoints = endpoints
				app = &newApp
				appMap[appPath] = app
			}
		}

		//compare and select min version
		if appVersionStr, ok := mApp.Env["SRY_APP_VSN"]; ok && appVersionStr < app.CurVsn {
			app.CurVsn = appVersionStr
			if endpointStr, ok := mApp.Env["BB_DM_ENDPOINTS"]; ok {
				endpoints := formEndpoints(endpointStr)
				app.Endpoints = endpoints
			}
		}

		tasks := formTasks(mApp, *app, tasksById)
		tasksJson, _ := json.Marshal(tasks)
		log.Println("tasks", string(tasksJson))
		app.Tasks = append(app.Tasks, tasks...)
		appJson, _ := json.Marshal(app)
		log.Println("app", string(appJson))
	}
	appMapJson, _ := json.Marshal(appMap)
	log.Println("app", string(appMapJson))

	apps := AppList{}
	for _, app := range appMap {
		apps = append(apps, *app)
	}
	return apps
}

func formTasks(mApp marathonApp, app App, tasksById map[string]marathonTaskList) []Task {
	tasks := []Task{}
	for _, mTask := range tasksById[mApp.Id] {
		if len(mTask.Ports) > 0 {
			server := fmt.Sprintf("%s-%s", app.Frontend, mTask.Host)
			t := Task{
				Frontend: app.Frontend,
				Server:   server,
				Host:     mTask.Host,
				Port:     mTask.Ports[0],
				Ports:    mTask.Ports,
				Version:  mApp.Env["SRY_APP_VSN"],
				Weight:   1,
			}
			tasks = append(tasks, t)
		}
	}
	return tasks
}

func formEndpoints(str string) []Endpoint {
	//{{ range $tcpIdx, $endpoint := Split $app.Env.BB_DM_ENDPOINTS "," }}
	//{{ $endpointSlices := Split $endpoint ":" }}
	//{{ $svcType := index $endpointSlices 0 }}
	//{{ $protocol := index $endpointSlices 1 }}
	//{{ $uri := index $endpointSlices 2 }}
	//{{ $port := index $endpointSlices 3 }}
	//# len {{ len $end
	//BB_DM_ENDPOINTS=pub:http:nil:9800,pub:tcp:nil:9801

	epStrSlices := strings.Split(str, ",")
	endpoints := []Endpoint{}
	for _, epStr := range epStrSlices {
		epParts := strings.Split(epStr, ":")
		if len(epParts) < 4 {
			continue
		}
		bind, err := strconv.Atoi(epParts[3])
		if err != nil {
			log.Panicln("bad bind value", err.Error())
		}
		endpoint := Endpoint{
			Protocol: epParts[1],
			Bind:     bind,
		}

		endpoints = append(endpoints, endpoint)
	}

	return endpoints
}

func formPath(mApp marathonApp) string {
	// Try to handle old app id format without slashes
	var appPath string
	if envAppId, ok := mApp.Env["SRY_APP_ID"]; ok {
		appPath = envAppId
	} else {
		appPath = mApp.Id
	}
	return strings.TrimPrefix(appPath, "/")
}

//formApp build App from marathonApp
func formApp(mApp marathonApp, appPath string) App {
	app := App{
		Id:              appPath,
		Frontend:        strings.Replace(appPath, "/", "::", -1),
		HealthCheckPath: parseHealthCheckPath(mApp.HealthChecks),
		Env:             mApp.Env,
		Labels:          mApp.Labels,
		CurVsn:          mApp.Env["SRY_APP_VSN"],
	}
	app.HealthChecks = make([]HealthCheck, 0, len(mApp.HealthChecks))
	for _, marathonCheck := range mApp.HealthChecks {
		check := HealthCheck{
			Protocol:  marathonCheck.Protocol,
			Path:      marathonCheck.Path,
			PortIndex: marathonCheck.PortIndex,
		}
		app.HealthChecks = append(app.HealthChecks, check)
	}

	if len(mApp.Ports) > 0 {
		app.ServicePort = mApp.Ports[0]
		app.ServicePorts = mApp.Ports
	}

	return app
}

func parseHealthCheckPath(checks []marathonHealthCheck) string {
	for _, check := range checks {
		if check.Protocol != "HTTP" {
			continue
		}
		return check.Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon, conf *configuration.Configuration) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url, conf)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string, conf *configuration.Configuration) (AppList, error) {
	tasks, err := fetchTasks(url, conf)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url, conf)
	if err != nil {
		return nil, err
	}
	log.Println("got mapps", len(marathonApps))
	log.Println("got mtasks", len(tasks))
	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<commit_msg>fix bug bamboo curVsn nil<commit_after>package marathon

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/QubitProducts/bamboo/configuration"
)

// Describes an app process running
type Task struct {
	Frontend string
	Server   string
	Host     string
	Port     int
	Ports    []int
	Version  string
	Weight   int
}

// A health check on the application
type HealthCheck struct {
	// One of TCP, HTTP or COMMAND
	Protocol string
	// The path (if Protocol is HTTP)
	Path string
	// The position of the port targeted in the ports array
	PortIndex int
}

type Endpoint struct {
	Protocol string
	Bind     int
}

// An app may have multiple processes
type App struct {
	Id              string
	Frontend        string
	HealthCheckPath string
	HealthChecks    []HealthCheck
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
	Labels          map[string]string
	Endpoints       []Endpoint
	CurVsn          string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonTaskList []marathonTask

type marathonTasks struct {
	Tasks marathonTaskList `json:"tasks"`
}

type marathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice marathonTaskList) Len() int {
	return len(slice)
}

func (slice marathonTaskList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice marathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonApps struct {
	Apps []marathonApp `json:"apps"`
}

type marathonApp struct {
	Id           string                `json:"id"`
	HealthChecks []marathonHealthCheck `json:"healthChecks"`
	Ports        []int                 `json:"ports"`
	Env          map[string]string     `json:"env"`
	Labels       map[string]string     `json:"labels"`
}

type marathonHealthCheck struct {
	Path      string `json:"path"`
	Protocol  string `json:"protocol"`
	PortIndex int    `json:"portIndex"`
}

func fetchMarathonApps(endpoint string, conf *configuration.Configuration) (map[string]marathonApp, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/apps", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	var appResponse marathonApps

	contents, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &appResponse)
	if err != nil {
		return nil, err
	}

	dataById := map[string]marathonApp{}

	for _, appConfig := range appResponse.Apps {
		dataById[appConfig.Id] = appConfig
	}

	return dataById, nil
}

func fetchTasks(endpoint string, conf *configuration.Configuration) (map[string]marathonTaskList, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	var tasks marathonTasks

	if err != nil {
		return nil, err
	}

	contents, err := ioutil.ReadAll(response.Body)
	defer response.Body.Close()
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &tasks)
	if err != nil {
		return nil, err
	}

	taskList := tasks.Tasks
	sort.Sort(taskList)

	tasksById := map[string]marathonTaskList{}
	for _, task := range taskList {
		if tasksById[task.AppId] == nil {
			tasksById[task.AppId] = marathonTaskList{}
		}
		tasksById[task.AppId] = append(tasksById[task.AppId], task)
	}

	for _, task_list := range tasksById {
		sort.Sort(task_list)
	}

	return tasksById, nil
}

func createApps(tasksById map[string]marathonTaskList, marathonApps map[string]marathonApp) AppList {
	appMap := map[string]*App{}
	for _, mApp := range marathonApps {
		mappJson, _ := json.Marshal(mApp)
		log.Println("mapp", string(mappJson))
		appPath := formPath(mApp)
		log.Println("appPath", string(appPath))
		app, ok := appMap[appPath]
		if !ok {
			newApp := formApp(mApp, appPath)

			if endpointStr, ok := mApp.Env["BB_DM_ENDPOINTS"]; ok {
				endpoints := formEndpoints(endpointStr)
				newApp.Endpoints = endpoints
			}
			app = &newApp
			appMap[appPath] = app
		}

		//compare and select min version
		if appVersionStr, ok := mApp.Env["SRY_APP_VSN"]; ok && appVersionStr < app.CurVsn {
			app.CurVsn = appVersionStr
			if endpointStr, ok := mApp.Env["BB_DM_ENDPOINTS"]; ok {
				endpoints := formEndpoints(endpointStr)
				app.Endpoints = endpoints
			}
		}

		tasks := formTasks(mApp, *app, tasksById)
		tasksJson, _ := json.Marshal(tasks)
		log.Println("tasks", string(tasksJson))
		app.Tasks = append(app.Tasks, tasks...)
		appJson, _ := json.Marshal(app)
		log.Println("app", string(appJson))
	}
	appMapJson, _ := json.Marshal(appMap)
	log.Println("app", string(appMapJson))

	apps := AppList{}
	for _, app := range appMap {
		apps = append(apps, *app)
	}
	return apps
}

func formTasks(mApp marathonApp, app App, tasksById map[string]marathonTaskList) []Task {
	tasks := []Task{}
	for _, mTask := range tasksById[mApp.Id] {
		if len(mTask.Ports) > 0 {
			server := fmt.Sprintf("%s-%s", app.Frontend, mTask.Host)
			t := Task{
				Frontend: app.Frontend,
				Server:   server,
				Host:     mTask.Host,
				Port:     mTask.Ports[0],
				Ports:    mTask.Ports,
				Version:  mApp.Env["SRY_APP_VSN"],
				Weight:   1,
			}
			tasks = append(tasks, t)
		}
	}
	return tasks
}

func formEndpoints(str string) []Endpoint {
	//{{ range $tcpIdx, $endpoint := Split $app.Env.BB_DM_ENDPOINTS "," }}
	//{{ $endpointSlices := Split $endpoint ":" }}
	//{{ $svcType := index $endpointSlices 0 }}
	//{{ $protocol := index $endpointSlices 1 }}
	//{{ $uri := index $endpointSlices 2 }}
	//{{ $port := index $endpointSlices 3 }}
	//# len {{ len $end
	//BB_DM_ENDPOINTS=pub:http:nil:9800,pub:tcp:nil:9801

	epStrSlices := strings.Split(str, ",")
	endpoints := []Endpoint{}
	for _, epStr := range epStrSlices {
		epParts := strings.Split(epStr, ":")
		if len(epParts) < 4 {
			continue
		}
		bind, err := strconv.Atoi(epParts[3])
		if err != nil {
			log.Panicln("bad bind value", err.Error())
		}
		endpoint := Endpoint{
			Protocol: epParts[1],
			Bind:     bind,
		}

		endpoints = append(endpoints, endpoint)
	}

	return endpoints
}

func formPath(mApp marathonApp) string {
	// Try to handle old app id format without slashes
	var appPath string
	if envAppId, ok := mApp.Env["SRY_APP_ID"]; ok {
		appPath = envAppId
	} else {
		appPath = mApp.Id
	}
	return strings.TrimPrefix(appPath, "/")
}

//formApp build App from marathonApp
func formApp(mApp marathonApp, appPath string) App {
	app := App{
		Id:              appPath,
		Frontend:        strings.Replace(appPath, "/", "::", -1),
		HealthCheckPath: parseHealthCheckPath(mApp.HealthChecks),
		Env:             mApp.Env,
		Labels:          mApp.Labels,
		CurVsn:          mApp.Env["SRY_APP_VSN"],
	}
	app.HealthChecks = make([]HealthCheck, 0, len(mApp.HealthChecks))
	for _, marathonCheck := range mApp.HealthChecks {
		check := HealthCheck{
			Protocol:  marathonCheck.Protocol,
			Path:      marathonCheck.Path,
			PortIndex: marathonCheck.PortIndex,
		}
		app.HealthChecks = append(app.HealthChecks, check)
	}

	if len(mApp.Ports) > 0 {
		app.ServicePort = mApp.Ports[0]
		app.ServicePorts = mApp.Ports
	}

	return app
}

func parseHealthCheckPath(checks []marathonHealthCheck) string {
	for _, check := range checks {
		if check.Protocol != "HTTP" {
			continue
		}
		return check.Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon, conf *configuration.Configuration) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url, conf)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string, conf *configuration.Configuration) (AppList, error) {
	tasks, err := fetchTasks(url, conf)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url, conf)
	if err != nil {
		return nil, err
	}
	log.Println("got mapps", len(marathonApps))
	log.Println("got mtasks", len(tasks))
	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<|endoftext|>


<commit_before>package marathon

import (
	"encoding/json"
	"github.com/QubitProducts/bamboo/configuration"
	"io/ioutil"
	"net/http"
	"sort"
	"strings"
)

// Describes an app process running
type Task struct {
	Host  string
	Port  int
	Ports []int
}

// An app may have multiple processes
type App struct {
	Id              string
	EscapedId       string
	HealthCheckPath string
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type MarathonTaskList []MarathonTask

type MarathonTasks struct {
	Tasks MarathonTaskList `json:tasks`
}

type MarathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice MarathonTaskList) Len() int {
	return len(slice)
}

func (slice MarathonTaskList) Less(i, j int) bool {
	return slice[i].StagedAt < slice[j].StagedAt
}

func (slice MarathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type MarathonApps struct {
	Apps []MarathonApp `json:apps`
}

type MarathonApp struct {
	Id           string            `json:id`
	HealthChecks []HealthChecks    `json:healthChecks`
	Ports        []int             `json:ports`
	Env          map[string]string `json:env`
}

type HealthChecks struct {
	Path string `json:path`
}

func fetchMarathonApps(endpoint string) (map[string]MarathonApp, error) {
	response, err := http.Get(endpoint + "/v2/apps")

	if err != nil {
		return nil, err
	} else {
		defer response.Body.Close()
		var appResponse MarathonApps

		contents, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(contents, &appResponse)
		if err != nil {
			return nil, err
		}

		dataById := map[string]MarathonApp{}

		for _, appConfig := range appResponse.Apps {
			dataById[appConfig.Id] = appConfig
		}

		return dataById, nil
	}
}

func fetchTasks(endpoint string) (map[string][]MarathonTask, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	response, err := client.Do(req)

	var tasks MarathonTasks

	if err != nil {
		return nil, err
	} else {
		contents, err := ioutil.ReadAll(response.Body)
		defer response.Body.Close()
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(contents, &tasks)
		if err != nil {
			return nil, err
		}

		taskList := tasks.Tasks
		sort.Sort(taskList)

		tasksById := map[string][]MarathonTask{}
		for _, task := range taskList {
			if tasksById[task.AppId] == nil {
				tasksById[task.AppId] = []MarathonTask{}
			}
			tasksById[task.AppId] = append(tasksById[task.AppId], task)
		}

		return tasksById, nil
	}
}

func createApps(tasksById map[string][]MarathonTask, marathonApps map[string]MarathonApp) AppList {

	apps := AppList{}

	for appId, tasks := range tasksById {
		simpleTasks := []Task{}

		for _, task := range tasks {
			if len(task.Ports) > 0 {
				simpleTasks = append(simpleTasks, Task{Host: task.Host, Port: task.Ports[0], Ports: task.Ports})
			}
		}

		// Try to handle old app id format without slashes
		appPath := appId
		if !strings.HasPrefix(appId, "/") {
			appPath = "/" + appId
		}

		app := App{
			// Since Marathon 0.7, apps are namespaced with path
			Id: appPath,
			// Used for template
			EscapedId:       strings.Replace(appId, "/", "::", -1),
			Tasks:           simpleTasks,
			HealthCheckPath: parseHealthCheckPath(marathonApps[appId].HealthChecks),
			Env:             marathonApps[appId].Env,
		}

		if len(marathonApps[appId].Ports) > 0 {
			app.ServicePort = marathonApps[appId].Ports[0]
			app.ServicePorts = marathonApps[appId].Ports
		}

		apps = append(apps, app)
	}
	return apps
}

func parseHealthCheckPath(checks []HealthChecks) string {
	if len(checks) > 0 {
		return checks[0].Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string) (AppList, error) {
	tasks, err := fetchTasks(url)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url)
	if err != nil {
		return nil, err
	}

	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<commit_msg>Only set the healthCheckPath if the test is HTTP<commit_after>package marathon

import (
	"encoding/json"
	"github.com/QubitProducts/bamboo/configuration"
	"io/ioutil"
	"net/http"
	"sort"
	"strings"
)

// Describes an app process running
type Task struct {
	Host  string
	Port  int
	Ports []int
}

// An app may have multiple processes
type App struct {
	Id              string
	EscapedId       string
	HealthCheckPath string
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type MarathonTaskList []MarathonTask

type MarathonTasks struct {
	Tasks MarathonTaskList `json:tasks`
}

type MarathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice MarathonTaskList) Len() int {
	return len(slice)
}

func (slice MarathonTaskList) Less(i, j int) bool {
	return slice[i].StagedAt < slice[j].StagedAt
}

func (slice MarathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type MarathonApps struct {
	Apps []MarathonApp `json:apps`
}

type MarathonApp struct {
	Id           string            `json:id`
	HealthChecks []HealthCheck    `json:healthChecks`
	Ports        []int             `json:ports`
	Env          map[string]string `json:env`
}

type HealthCheck struct {
	Path string `json:path`
	Protocol string `json:protocol`
}

func fetchMarathonApps(endpoint string) (map[string]MarathonApp, error) {
	response, err := http.Get(endpoint + "/v2/apps")

	if err != nil {
		return nil, err
	} else {
		defer response.Body.Close()
		var appResponse MarathonApps

		contents, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(contents, &appResponse)
		if err != nil {
			return nil, err
		}

		dataById := map[string]MarathonApp{}

		for _, appConfig := range appResponse.Apps {
			dataById[appConfig.Id] = appConfig
		}

		return dataById, nil
	}
}

func fetchTasks(endpoint string) (map[string][]MarathonTask, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	response, err := client.Do(req)

	var tasks MarathonTasks

	if err != nil {
		return nil, err
	} else {
		contents, err := ioutil.ReadAll(response.Body)
		defer response.Body.Close()
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(contents, &tasks)
		if err != nil {
			return nil, err
		}

		taskList := tasks.Tasks
		sort.Sort(taskList)

		tasksById := map[string][]MarathonTask{}
		for _, task := range taskList {
			if tasksById[task.AppId] == nil {
				tasksById[task.AppId] = []MarathonTask{}
			}
			tasksById[task.AppId] = append(tasksById[task.AppId], task)
		}

		return tasksById, nil
	}
}

func createApps(tasksById map[string][]MarathonTask, marathonApps map[string]MarathonApp) AppList {

	apps := AppList{}

	for appId, tasks := range tasksById {
		simpleTasks := []Task{}

		for _, task := range tasks {
			if len(task.Ports) > 0 {
				simpleTasks = append(simpleTasks, Task{Host: task.Host, Port: task.Ports[0], Ports: task.Ports})
			}
		}

		// Try to handle old app id format without slashes
		appPath := appId
		if !strings.HasPrefix(appId, "/") {
			appPath = "/" + appId
		}

		app := App{
			// Since Marathon 0.7, apps are namespaced with path
			Id: appPath,
			// Used for template
			EscapedId:       strings.Replace(appId, "/", "::", -1),
			Tasks:           simpleTasks,
			HealthCheckPath: parseHealthCheckPath(marathonApps[appId].HealthChecks),
			Env:             marathonApps[appId].Env,
		}

		if len(marathonApps[appId].Ports) > 0 {
			app.ServicePort = marathonApps[appId].Ports[0]
			app.ServicePorts = marathonApps[appId].Ports
		}

		apps = append(apps, app)
	}
	return apps
}

func parseHealthCheckPath(checks []HealthCheck) string {
	for _, check := range checks {
		if check.Protocol != "HTTP" {
			continue
		}
		return check.Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string) (AppList, error) {
	tasks, err := fetchTasks(url)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url)
	if err != nil {
		return nil, err
	}

	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<|endoftext|>


<commit_before>package curler

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

// New creates a new curler
func New(orig http.Handler, out io.Writer) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		parts := []string{"curl", "-v", "-X", r.Method}

		for k, v := range r.Header {
			ck := http.CanonicalHeaderKey(k)
			if ck != "Host" && ck != "User-Agent" {
				parts = append(parts, "-H", fmt.Sprintf("'%s: %s'", k, strings.Join(v, ",")))
			}
		}

		if r.Method == "POST" || r.Method == "PATCH" || r.Method == "PUT" {
			b, err := ioutil.ReadAll(r.Body)

			if err != nil {
				fmt.Fprintln(out, "curler:", err)
			}
			if err == nil && len(b) > 0 {
				r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
				parts = append(parts, "-d '", string(b)+"'")
			}
		}

		parts = append(parts, r.URL.String())
		fmt.Fprintln(out, strings.Join(parts, " "))
		orig.ServeHTTP(rw, r)
	})
}
<commit_msg>capture host and scheme<commit_after>package curler

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

// New creates a new curler
func New(orig http.Handler, out io.Writer) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		parts := []string{"curl", "-i", "-X", r.Method}

		for k, v := range r.Header {
			ck := http.CanonicalHeaderKey(k)
			if ck != "Host" && ck != "User-Agent" {
				parts = append(parts, "-H", fmt.Sprintf("'%s: %s'", k, strings.Join(v, ",")))
			}
		}

		if r.Method == "POST" || r.Method == "PATCH" || r.Method == "PUT" {
			b, err := ioutil.ReadAll(r.Body)

			if err != nil {
				fmt.Fprintln(out, "curler:", err)
			}
			if err == nil && len(b) > 0 {
				r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
				parts = append(parts, "-d '", string(b)+"'")
			}
		}
		scheme := "http"
		if r.TLS != nil {
			scheme += "s"
		}
		fmt.Println("host:", r.Host, "proto:", r.Proto, "scheme:", r.TLS != nil)
		parts = append(parts, scheme+"://"+r.Host+r.URL.String())
		fmt.Fprintln(out, strings.Join(parts, " "))
		orig.ServeHTTP(rw, r)
	})
}
<|endoftext|>


<commit_before>/*
CompileDaemon is a very simple compile daemon for Go.

CompileDaemon watches your .go files in a directory and invokes `go build`
if a file changes.

Examples

In its simplest form, the defaults will do. With the current working directory set
to the source directory you can simply…

    $ CompileDaemon

… and it will recompile your code whenever you save a source file.

If you want it to also run your program each time it builds you might add…

    $ CompileDaemon -command="./MyProgram -my-options"

… and it will also keep a copy of your program running. Killing the old one and
starting a new one each time you build. For advanced usage you can also supply
the changed file to the command by doing…

	$ CompileDaemon -command="./MyProgram -my-options %[1]s"

…but note that this will not be set on the first start.

You may find that you need to exclude some directories and files from
monitoring, such as a .git repository or emacs temporary files…

    $ CompileDaemon -exclude-dir=.git -exclude=".#*"

If you want to monitor files other than .go and .c files you might…

    $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl"

Options

There are command line options.

	FILE SELECTION
	-directory=XXX    – Which directory to monitor for changes
	-recursive=XXX    – Look into subdirectories
	-exclude-dir=XXX  – Exclude directories matching glob pattern XXX
	-exlude=XXX       – Exclude files whose basename matches glob pattern XXX
	-include=XXX      – Include files whose basename matches glob pattern XXX
	-pattern=XXX      – Include files whose path matches regexp XXX

	MISC
	-color            - Enable colorized output
	-log-prefix       - Enable/disable stdout/stderr labelling for the child process
	-graceful-kill    - On supported platforms, send the child process a SIGTERM to
	                    allow it to exit gracefully if possible.
	-graceful-timeout - Duration (in seconds) to wait for graceful kill to complete
	-verbose          - Print information about watched directories.

	ACTIONS
	-build=CCC        – Execute CCC to rebuild when a file changes
	-command=CCC      – Run command CCC after a successful build, stops previous command first

*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/fatih/color"
	"gopkg.in/fsnotify.v1"
)

// Milliseconds to wait for the next job to begin after a file change
const WorkDelay = 900

// Default pattern to match files which trigger a build
const FilePattern = `(.+\.go|.+\.c)$`

type globList []string

func (g *globList) String() string {
	return fmt.Sprint(*g)
}
func (g *globList) Set(value string) error {
	*g = append(*g, filepath.Clean(value))
	return nil
}
func (g *globList) Matches(value string) bool {
	for _, v := range *g {
		if match, err := filepath.Match(v, value); err != nil {
			log.Fatalf("Bad pattern \"%s\": %s", v, err.Error())
		} else if match {
			return true
		}
	}
	return false
}

var (
	flag_directory       = flag.String("directory", ".", "Directory to watch for changes")
	flag_pattern         = flag.String("pattern", FilePattern, "Pattern of watched files")
	flag_command         = flag.String("command", "", "Command to run and restart after build")
	flag_command_stop    = flag.Bool("command-stop", false, "Stop command before building")
	flag_recursive       = flag.Bool("recursive", true, "Watch all dirs. recursively")
	flag_build           = flag.String("build", "go build", "Command to rebuild after changes")
	flag_build_dir       = flag.String("build-dir", "", "Directory to run build command in.  Defaults to directory")
	flag_color           = flag.Bool("color", false, "Colorize output for CompileDaemon status messages")
	flag_logprefix       = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output")
	flag_gracefulkill    = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first")
	flag_gracefultimeout = flag.Uint("graceful-timeout", 3, "Duration (in seconds) to wait for graceful kill to complete")
	flag_verbose         = flag.Bool("verbose", false, "Be verbose about which directories are watched.")

	// initialized in main() due to custom type.
	flag_excludedDirs  globList
	flag_excludedFiles globList
	flag_includedFiles globList
)

func okColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.GreenString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

func failColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.RedString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

// Run `go build` and print the output if something's gone wrong.
func build() bool {
	log.Println(okColor("Running build command!"))

	args := strings.Split(*flag_build, " ")
	if len(args) == 0 {
		// If the user has specified and empty then we are done.
		return true
	}

	cmd := exec.Command(args[0], args[1:]...)

	if *flag_build_dir != "" {
		cmd.Dir = *flag_build_dir
	} else {
		cmd.Dir = *flag_directory
	}

	output, err := cmd.CombinedOutput()

	if err == nil {
		log.Println(okColor("Build ok."))
	} else {
		log.Println(failColor("Error while building:\n"), failColor(string(output)))
	}

	return err == nil
}

func matchesPattern(pattern *regexp.Regexp, file string) bool {
	return pattern.MatchString(file)
}

// Accept build jobs and start building when there are no jobs rushing in.
// The inrush protection is WorkDelay milliseconds long, in this period
// every incoming job will reset the timer.
func builder(jobs <-chan string, buildStarted chan<- string, buildDone chan<- bool) {
	createThreshold := func() <-chan time.Time {
		return time.After(time.Duration(WorkDelay * time.Millisecond))
	}

	threshold := createThreshold()
	eventPath := ""

	for {
		select {
		case eventPath = <-jobs:
			threshold = createThreshold()
		case <-threshold:
			buildStarted <- eventPath
			buildDone <- build()
		}
	}
}

func logger(pipeChan <-chan io.ReadCloser) {
	dumper := func(pipe io.ReadCloser, prefix string) {
		reader := bufio.NewReader(pipe)

	readloop:
		for {
			line, err := reader.ReadString('\n')

			if err != nil {
				break readloop
			}

			if *flag_logprefix {
				log.Print(prefix, " ", line)
			} else {
				log.Print(line)
			}
		}
	}

	for {
		pipe := <-pipeChan
		go dumper(pipe, "stdout:")

		pipe = <-pipeChan
		go dumper(pipe, "stderr:")
	}
}

// Start the supplied command and return stdout and stderr pipes for logging.
func startCommand(command string) (cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, err error) {
	args := strings.Split(command, " ")
	cmd = exec.Command(args[0], args[1:]...)

	if stdout, err = cmd.StdoutPipe(); err != nil {
		err = fmt.Errorf("can't get stdout pipe for command: %s", err)
		return
	}

	if stderr, err = cmd.StderrPipe(); err != nil {
		err = fmt.Errorf("can't get stderr pipe for command: %s", err)
		return
	}

	if err = cmd.Start(); err != nil {
		err = fmt.Errorf("can't start command: %s", err)
		return
	}

	return
}

// Run the command in the given string and restart it after
// a message was received on the buildDone channel.
func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-chan bool) {
	var currentProcess *os.Process
	pipeChan := make(chan io.ReadCloser)

	go logger(pipeChan)

	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, fatalSignals...)
		<-sigChan
		log.Println(okColor("Received signal, terminating cleanly."))
		if currentProcess != nil {
			killProcess(currentProcess)
		}
		os.Exit(0)
	}()

	for {
		eventPath := <-buildStarted

		// append %0.s to use format specifier even if not supplied by user
		// to suppress warning in returned string.
		command := fmt.Sprintf("%0.s"+commandTemplate, eventPath)

		if !*flag_command_stop {
			if !<-buildSuccess {
				continue
			}
		}

		if currentProcess != nil {
			killProcess(currentProcess)
		}
		if *flag_command_stop {
			log.Println(okColor("Command stopped. Waiting for build to complete."))
			if !<-buildSuccess {
				continue
			}
		}

		log.Println(okColor("Restarting the given command."))
		cmd, stdoutPipe, stderrPipe, err := startCommand(command)

		if err != nil {
			log.Fatal(failColor("Could not start command: %s", err))
		}

		pipeChan <- stdoutPipe
		pipeChan <- stderrPipe

		currentProcess = cmd.Process
	}
}

func killProcess(process *os.Process) {
	if *flag_gracefulkill {
		killProcessGracefully(process)
	} else {
		killProcessHard(process)
	}
}

func killProcessHard(process *os.Process) {
	log.Println(okColor("Hard stopping the current process.."))

	if err := process.Kill(); err != nil {
		log.Println(failColor("Warning: could not kill child process.  It may have already exited."))
	}

	if _, err := process.Wait(); err != nil {
		log.Fatal(failColor("Could not wait for child process. Aborting due to danger of infinite forks."))
	}
}

func killProcessGracefully(process *os.Process) {
	done := make(chan error, 1)
	go func() {
		log.Println(okColor("Gracefully stopping the current process.."))
		if err := terminateGracefully(process); err != nil {
			done <- err
			return
		}
		_, err := process.Wait()
		done <- err
	}()

	select {
	case <-time.After(time.Duration(*flag_gracefultimeout) * time.Second):
		log.Println(failColor("Could not gracefully stop the current process, proceeding to hard stop."))
		killProcessHard(process)
		<-done
	case err := <-done:
		if err != nil {
			log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
		}
	}
}

func flusher(buildStarted <-chan string, buildSuccess <-chan bool) {
	for {
		<-buildStarted
		<-buildSuccess
	}
}

func main() {
	flag.Var(&flag_excludedDirs, "exclude-dir", " Don't watch directories matching this name")
	flag.Var(&flag_excludedFiles, "exclude", " Don't watch files matching this name")
	flag.Var(&flag_includedFiles, "include", " Watch files matching this name")

	flag.Parse()

	if !*flag_logprefix {
		log.SetFlags(0)
	}

	if *flag_directory == "" {
		fmt.Fprintf(os.Stderr, "-directory=... is required.\n")
		os.Exit(1)
	}

	if *flag_gracefulkill && !gracefulTerminationPossible() {
		log.Fatal("Graceful termination is not supported on your platform.")
	}

	watcher, err := fsnotify.NewWatcher()

	if err != nil {
		log.Fatal(err)
	}

	defer watcher.Close()

	if *flag_recursive == true {
		err = filepath.Walk(*flag_directory, func(path string, info os.FileInfo, err error) error {
			if err == nil && info.IsDir() {
				if flag_excludedDirs.Matches(path) {
					return filepath.SkipDir
				} else {
					if *flag_verbose {
						log.Printf("Watching directory '%s' for changes.\n", path)
					}
					return watcher.Add(path)
				}
			}
			return err
		})

		if err != nil {
			log.Fatal("filepath.Walk():", err)
		}

		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}

	} else {
		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}
	}

	pattern := regexp.MustCompile(*flag_pattern)
	jobs := make(chan string)
	buildSuccess := make(chan bool)
	buildStarted := make(chan string)

	go builder(jobs, buildStarted, buildSuccess)

	if *flag_command != "" {
		go runner(*flag_command, buildStarted, buildSuccess)
	} else {
		go flusher(buildStarted, buildSuccess)
	}

	for {
		select {
		case ev := <-watcher.Events:
			if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
				base := filepath.Base(ev.Name)

				// Assume it is a directory and track it.
				if *flag_recursive == true && !flag_excludedDirs.Matches(ev.Name) {
					watcher.Add(ev.Name)
				}

				if flag_includedFiles.Matches(base) || matchesPattern(pattern, ev.Name) {
					if !flag_excludedFiles.Matches(base) {
						jobs <- ev.Name
					}
				}
			}

		case err := <-watcher.Errors:
			if v, ok := err.(*os.SyscallError); ok {
				if v.Err == syscall.EINTR {
					continue
				}
				log.Fatal("watcher.Error: SyscallError:", v)
			}
			log.Fatal("watcher.Error:", err)
		}
	}
}
<commit_msg>Use updated fsnotify repository<commit_after>/*
CompileDaemon is a very simple compile daemon for Go.

CompileDaemon watches your .go files in a directory and invokes `go build`
if a file changes.

Examples

In its simplest form, the defaults will do. With the current working directory set
to the source directory you can simply…

    $ CompileDaemon

… and it will recompile your code whenever you save a source file.

If you want it to also run your program each time it builds you might add…

    $ CompileDaemon -command="./MyProgram -my-options"

… and it will also keep a copy of your program running. Killing the old one and
starting a new one each time you build. For advanced usage you can also supply
the changed file to the command by doing…

	$ CompileDaemon -command="./MyProgram -my-options %[1]s"

…but note that this will not be set on the first start.

You may find that you need to exclude some directories and files from
monitoring, such as a .git repository or emacs temporary files…

    $ CompileDaemon -exclude-dir=.git -exclude=".#*"

If you want to monitor files other than .go and .c files you might…

    $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl"

Options

There are command line options.

	FILE SELECTION
	-directory=XXX    – Which directory to monitor for changes
	-recursive=XXX    – Look into subdirectories
	-exclude-dir=XXX  – Exclude directories matching glob pattern XXX
	-exlude=XXX       – Exclude files whose basename matches glob pattern XXX
	-include=XXX      – Include files whose basename matches glob pattern XXX
	-pattern=XXX      – Include files whose path matches regexp XXX

	MISC
	-color            - Enable colorized output
	-log-prefix       - Enable/disable stdout/stderr labelling for the child process
	-graceful-kill    - On supported platforms, send the child process a SIGTERM to
	                    allow it to exit gracefully if possible.
	-graceful-timeout - Duration (in seconds) to wait for graceful kill to complete
	-verbose          - Print information about watched directories.

	ACTIONS
	-build=CCC        – Execute CCC to rebuild when a file changes
	-command=CCC      – Run command CCC after a successful build, stops previous command first

*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/fatih/color"
	"github.com/fsnotify/fsnotify"
)

// Milliseconds to wait for the next job to begin after a file change
const WorkDelay = 900

// Default pattern to match files which trigger a build
const FilePattern = `(.+\.go|.+\.c)$`

type globList []string

func (g *globList) String() string {
	return fmt.Sprint(*g)
}
func (g *globList) Set(value string) error {
	*g = append(*g, filepath.Clean(value))
	return nil
}
func (g *globList) Matches(value string) bool {
	for _, v := range *g {
		if match, err := filepath.Match(v, value); err != nil {
			log.Fatalf("Bad pattern \"%s\": %s", v, err.Error())
		} else if match {
			return true
		}
	}
	return false
}

var (
	flag_directory       = flag.String("directory", ".", "Directory to watch for changes")
	flag_pattern         = flag.String("pattern", FilePattern, "Pattern of watched files")
	flag_command         = flag.String("command", "", "Command to run and restart after build")
	flag_command_stop    = flag.Bool("command-stop", false, "Stop command before building")
	flag_recursive       = flag.Bool("recursive", true, "Watch all dirs. recursively")
	flag_build           = flag.String("build", "go build", "Command to rebuild after changes")
	flag_build_dir       = flag.String("build-dir", "", "Directory to run build command in.  Defaults to directory")
	flag_color           = flag.Bool("color", false, "Colorize output for CompileDaemon status messages")
	flag_logprefix       = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output")
	flag_gracefulkill    = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first")
	flag_gracefultimeout = flag.Uint("graceful-timeout", 3, "Duration (in seconds) to wait for graceful kill to complete")
	flag_verbose         = flag.Bool("verbose", false, "Be verbose about which directories are watched.")

	// initialized in main() due to custom type.
	flag_excludedDirs  globList
	flag_excludedFiles globList
	flag_includedFiles globList
)

func okColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.GreenString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

func failColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.RedString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

// Run `go build` and print the output if something's gone wrong.
func build() bool {
	log.Println(okColor("Running build command!"))

	args := strings.Split(*flag_build, " ")
	if len(args) == 0 {
		// If the user has specified and empty then we are done.
		return true
	}

	cmd := exec.Command(args[0], args[1:]...)

	if *flag_build_dir != "" {
		cmd.Dir = *flag_build_dir
	} else {
		cmd.Dir = *flag_directory
	}

	output, err := cmd.CombinedOutput()

	if err == nil {
		log.Println(okColor("Build ok."))
	} else {
		log.Println(failColor("Error while building:\n"), failColor(string(output)))
	}

	return err == nil
}

func matchesPattern(pattern *regexp.Regexp, file string) bool {
	return pattern.MatchString(file)
}

// Accept build jobs and start building when there are no jobs rushing in.
// The inrush protection is WorkDelay milliseconds long, in this period
// every incoming job will reset the timer.
func builder(jobs <-chan string, buildStarted chan<- string, buildDone chan<- bool) {
	createThreshold := func() <-chan time.Time {
		return time.After(time.Duration(WorkDelay * time.Millisecond))
	}

	threshold := createThreshold()
	eventPath := ""

	for {
		select {
		case eventPath = <-jobs:
			threshold = createThreshold()
		case <-threshold:
			buildStarted <- eventPath
			buildDone <- build()
		}
	}
}

func logger(pipeChan <-chan io.ReadCloser) {
	dumper := func(pipe io.ReadCloser, prefix string) {
		reader := bufio.NewReader(pipe)

	readloop:
		for {
			line, err := reader.ReadString('\n')

			if err != nil {
				break readloop
			}

			if *flag_logprefix {
				log.Print(prefix, " ", line)
			} else {
				log.Print(line)
			}
		}
	}

	for {
		pipe := <-pipeChan
		go dumper(pipe, "stdout:")

		pipe = <-pipeChan
		go dumper(pipe, "stderr:")
	}
}

// Start the supplied command and return stdout and stderr pipes for logging.
func startCommand(command string) (cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, err error) {
	args := strings.Split(command, " ")
	cmd = exec.Command(args[0], args[1:]...)

	if stdout, err = cmd.StdoutPipe(); err != nil {
		err = fmt.Errorf("can't get stdout pipe for command: %s", err)
		return
	}

	if stderr, err = cmd.StderrPipe(); err != nil {
		err = fmt.Errorf("can't get stderr pipe for command: %s", err)
		return
	}

	if err = cmd.Start(); err != nil {
		err = fmt.Errorf("can't start command: %s", err)
		return
	}

	return
}

// Run the command in the given string and restart it after
// a message was received on the buildDone channel.
func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-chan bool) {
	var currentProcess *os.Process
	pipeChan := make(chan io.ReadCloser)

	go logger(pipeChan)

	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, fatalSignals...)
		<-sigChan
		log.Println(okColor("Received signal, terminating cleanly."))
		if currentProcess != nil {
			killProcess(currentProcess)
		}
		os.Exit(0)
	}()

	for {
		eventPath := <-buildStarted

		// append %0.s to use format specifier even if not supplied by user
		// to suppress warning in returned string.
		command := fmt.Sprintf("%0.s"+commandTemplate, eventPath)

		if !*flag_command_stop {
			if !<-buildSuccess {
				continue
			}
		}

		if currentProcess != nil {
			killProcess(currentProcess)
		}
		if *flag_command_stop {
			log.Println(okColor("Command stopped. Waiting for build to complete."))
			if !<-buildSuccess {
				continue
			}
		}

		log.Println(okColor("Restarting the given command."))
		cmd, stdoutPipe, stderrPipe, err := startCommand(command)

		if err != nil {
			log.Fatal(failColor("Could not start command: %s", err))
		}

		pipeChan <- stdoutPipe
		pipeChan <- stderrPipe

		currentProcess = cmd.Process
	}
}

func killProcess(process *os.Process) {
	if *flag_gracefulkill {
		killProcessGracefully(process)
	} else {
		killProcessHard(process)
	}
}

func killProcessHard(process *os.Process) {
	log.Println(okColor("Hard stopping the current process.."))

	if err := process.Kill(); err != nil {
		log.Println(failColor("Warning: could not kill child process.  It may have already exited."))
	}

	if _, err := process.Wait(); err != nil {
		log.Fatal(failColor("Could not wait for child process. Aborting due to danger of infinite forks."))
	}
}

func killProcessGracefully(process *os.Process) {
	done := make(chan error, 1)
	go func() {
		log.Println(okColor("Gracefully stopping the current process.."))
		if err := terminateGracefully(process); err != nil {
			done <- err
			return
		}
		_, err := process.Wait()
		done <- err
	}()

	select {
	case <-time.After(time.Duration(*flag_gracefultimeout) * time.Second):
		log.Println(failColor("Could not gracefully stop the current process, proceeding to hard stop."))
		killProcessHard(process)
		<-done
	case err := <-done:
		if err != nil {
			log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
		}
	}
}

func flusher(buildStarted <-chan string, buildSuccess <-chan bool) {
	for {
		<-buildStarted
		<-buildSuccess
	}
}

func main() {
	flag.Var(&flag_excludedDirs, "exclude-dir", " Don't watch directories matching this name")
	flag.Var(&flag_excludedFiles, "exclude", " Don't watch files matching this name")
	flag.Var(&flag_includedFiles, "include", " Watch files matching this name")

	flag.Parse()

	if !*flag_logprefix {
		log.SetFlags(0)
	}

	if *flag_directory == "" {
		fmt.Fprintf(os.Stderr, "-directory=... is required.\n")
		os.Exit(1)
	}

	if *flag_gracefulkill && !gracefulTerminationPossible() {
		log.Fatal("Graceful termination is not supported on your platform.")
	}

	watcher, err := fsnotify.NewWatcher()

	if err != nil {
		log.Fatal(err)
	}

	defer watcher.Close()

	if *flag_recursive == true {
		err = filepath.Walk(*flag_directory, func(path string, info os.FileInfo, err error) error {
			if err == nil && info.IsDir() {
				if flag_excludedDirs.Matches(path) {
					return filepath.SkipDir
				} else {
					if *flag_verbose {
						log.Printf("Watching directory '%s' for changes.\n", path)
					}
					return watcher.Add(path)
				}
			}
			return err
		})

		if err != nil {
			log.Fatal("filepath.Walk():", err)
		}

		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}

	} else {
		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}
	}

	pattern := regexp.MustCompile(*flag_pattern)
	jobs := make(chan string)
	buildSuccess := make(chan bool)
	buildStarted := make(chan string)

	go builder(jobs, buildStarted, buildSuccess)

	if *flag_command != "" {
		go runner(*flag_command, buildStarted, buildSuccess)
	} else {
		go flusher(buildStarted, buildSuccess)
	}

	for {
		select {
		case ev := <-watcher.Events:
			if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
				base := filepath.Base(ev.Name)

				// Assume it is a directory and track it.
				if *flag_recursive == true && !flag_excludedDirs.Matches(ev.Name) {
					watcher.Add(ev.Name)
				}

				if flag_includedFiles.Matches(base) || matchesPattern(pattern, ev.Name) {
					if !flag_excludedFiles.Matches(base) {
						jobs <- ev.Name
					}
				}
			}

		case err := <-watcher.Errors:
			if v, ok := err.(*os.SyscallError); ok {
				if v.Err == syscall.EINTR {
					continue
				}
				log.Fatal("watcher.Error: SyscallError:", v)
			}
			log.Fatal("watcher.Error:", err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"polydawn.net/siphon"
	"time"
	"strings"
	"syscall"
)

type daemonOpts struct {
	Address string `short:"L" long:"addr" optional:"true" default:"defaults to unix://siphon.daemon.sock" description:"Address to bind to and await client attachings, of the form unix://path/to/socket.  Each new client connection will result in the spawning of a new siphon host process, which the client will be transparently redirected to."`
	HostAddress string `short:"H" long:"host-addr" optional:"true" default:"defaults to unix://siphon.#####.sock" description:"Pattern of addresses to create new hosts at, of the form unix://path/to/socket.  '#' characters will be replaced by a random [1-9] digit (the number of hosts this daemon can spawn is implicitly limited to 9^{the number of '#' chars})." `
	HostCommand string `short:"c" long:"host-command" optional:"true" default:"defaults to /bin/sh" description:"Command to execute inside the new psuedoterminal" `
}

func init() {
	parser.AddCommand("daemon", "daemon", "Run a daemon to spawn hosts", &daemonOpts{
		Address: "unix://siphon.daemon.sock",
		HostAddress: "unix://siphon.#####.sock",
		HostCommand: "/bin/sh",
	})
}

// honestly, just for function grouping
type Daemon struct {
	opts *daemonOpts
}

func (opts *daemonOpts) Execute(args []string) error {
	addr, err := ParseNewAddr(opts.Address)
	if err != nil {
		fmt.Fprintf(os.Stderr, "siphon: %s\n", err)
		os.Exit(EXIT_BADARGS)
	}

	fmt.Printf("Serving daemon at %s\n", addr.Label)
	daemon := &Daemon{ opts: opts }

	listener, err := net.Listen(addr.Proto, addr.Addr)
	if err != nil {
		panic(err)
	}
	for listener != nil {
		conn, err := listener.Accept();
		if err != nil {
			if err.(*net.OpError).Err.Error() == "use of closed network connection" {
				break
			}
			panic(err)
		}
		fmt.Printf("siphon: daemon: accepted new client connection %p\r\n", conn)
		go daemon.handleRemoteClient(siphon.NewNetConn(conn))
	}

	return nil
}

func (daemon *Daemon) handleRemoteClient(conn *siphon.Conn) {
	defer conn.Close()

	// do startup handshake
	var hai siphon.Hello
	if err := conn.Decode(&hai); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}
	if hai.Siphon != "siphon" {
		fmt.Printf("siphon: daemon: Encountered a non-siphon protocol, dropping client %s\n", conn.Label())
		return
	}
	if hai.Hello != "client" {
		fmt.Printf("siphon: daemon: Unexpected hello from not a client protocol, dropping client %s\n", conn.Label())
		return
	}
	if err := conn.Encode(siphon.HelloAck{
		Siphon: "siphon",
		Hello: "daemon",
	}); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}

	addr := daemon.launchHost()
	fmt.Printf("siphon: daemon: launched host at %s for client %s\n", addr.Label, conn.Label())

	if err := conn.Encode(siphon.Redirect{
		Addr: addr,
	}); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}
}

func (daemon *Daemon) launchHost() siphon.Addr {
	for {
		siphonHostAddrStr := strings.Map(
			func(r rune) rune {
				switch r {
				case '#':
					return []rune(fmt.Sprintf("%d", rand.Int31n(9)+1))[0]
				default:
					return r
				}
			},
			daemon.opts.HostAddress,
		)

		addr, addrErr := ParseNewAddr(siphonHostAddrStr)
		if addrErr != nil {
			panic(addrErr)
		}

		fmt.Printf("siphon: daemon: attempting to launch host with address %s\n", addr.Label)
		err := daemon.attemptLaunchHost(siphonHostAddrStr)
		if err == errDaemonRetryHost {
			continue
		} else if err != nil {
			panic(err)
		} else {
			// returning a siphon.Addr struct is a bit of a clusterfuck, since we also have to be honest
			//  with ourselves that we just exec'd and passed the string form across a shell.  But would
			//  spitting that string across the network be any better?  No.  At least this way, you're
			//  only assuming that the siphon processes on the daemon side are both parsing the string
			//  the same way, and that seems ever so slightly less reckless.
			return addr
		}
	}
}

var errDaemonRetryHost = fmt.Errorf("siphon: daemon: requested host bind address already in use")

/**
 * Try to make a host at a chosen address.
 *
 * This may be rejected because the bind attempt finds the address already consumed, in which case it's
 * appropriate to simply choose a new random address and try this function again.
 */
func (daemon *Daemon) attemptLaunchHost(addr string) error {
	args := []string{
		"host",
		"-L", addr,
		"-c", daemon.opts.HostCommand,
	}
	cmd := exec.Command("siphon", args...)

	if err := cmd.Start(); err != nil {
		panic(err)
	}

	exitCode := -1
	exitCh := make(chan bool)
	go func() {
		defer close(exitCh)
		err := cmd.Wait()
		if exitError, ok := err.(*exec.ExitError); ok {
			if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
				exitCode = waitStatus.ExitStatus()
			} else { panic(exitError); }
		}
	}()

	// TODO: this entire wait is nonsense, and what we should really do is have the host send a message back to this daemon that parented it over stdout.
	//  But if we're going to start using stdout for business, we're going to tear a lot of shoddy logging statements out, add a non-for-humans mode, and take output format a great deal more seriously.
	select {
	case <- exitCh :
		if exitCode == 0 {
			// we probably don't actually expect the hosted process to return so quickly, so maybe this should later be some kind fo error
			return nil
		} else if exitCode == EXIT_BIND_IN_USE {
			return errDaemonRetryHost
		} else {
			return fmt.Errorf("siphon: daemon: unrecognized problem in spawning host (exit code %d)", exitCode)
		}
	case <- time.After(100 * time.Millisecond) :
		return nil
	}
}
<commit_msg>change the default socket location for daemons to be the same as the host command.<commit_after>package main

import (
	"fmt"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"polydawn.net/siphon"
	"time"
	"strings"
	"syscall"
)

type daemonOpts struct {
	Address string `short:"L" long:"addr" optional:"true" default:"defaults to unix://siphon.sock" description:"Address to bind to and await client attachings, of the form unix://path/to/socket.  Each new client connection will result in the spawning of a new siphon host process, which the client will be transparently redirected to."`
	HostAddress string `short:"H" long:"host-addr" optional:"true" default:"defaults to unix://siphon.#####.sock" description:"Pattern of addresses to create new hosts at, of the form unix://path/to/socket.  '#' characters will be replaced by a random [1-9] digit (the number of hosts this daemon can spawn is implicitly limited to 9^{the number of '#' chars})." `
	HostCommand string `short:"c" long:"host-command" optional:"true" default:"defaults to /bin/sh" description:"Command to execute inside the new psuedoterminal" `
}

func init() {
	parser.AddCommand("daemon", "daemon", "Run a daemon to spawn hosts", &daemonOpts{
		Address: "unix://siphon.sock",
		HostAddress: "unix://siphon.#####.sock",
		HostCommand: "/bin/sh",
	})
}

// honestly, just for function grouping
type Daemon struct {
	opts *daemonOpts
}

func (opts *daemonOpts) Execute(args []string) error {
	addr, err := ParseNewAddr(opts.Address)
	if err != nil {
		fmt.Fprintf(os.Stderr, "siphon: %s\n", err)
		os.Exit(EXIT_BADARGS)
	}

	fmt.Printf("Serving daemon at %s\n", addr.Label)
	daemon := &Daemon{ opts: opts }

	listener, err := net.Listen(addr.Proto, addr.Addr)
	if err != nil {
		panic(err)
	}
	for listener != nil {
		conn, err := listener.Accept();
		if err != nil {
			if err.(*net.OpError).Err.Error() == "use of closed network connection" {
				break
			}
			panic(err)
		}
		fmt.Printf("siphon: daemon: accepted new client connection %p\r\n", conn)
		go daemon.handleRemoteClient(siphon.NewNetConn(conn))
	}

	return nil
}

func (daemon *Daemon) handleRemoteClient(conn *siphon.Conn) {
	defer conn.Close()

	// do startup handshake
	var hai siphon.Hello
	if err := conn.Decode(&hai); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}
	if hai.Siphon != "siphon" {
		fmt.Printf("siphon: daemon: Encountered a non-siphon protocol, dropping client %s\n", conn.Label())
		return
	}
	if hai.Hello != "client" {
		fmt.Printf("siphon: daemon: Unexpected hello from not a client protocol, dropping client %s\n", conn.Label())
		return
	}
	if err := conn.Encode(siphon.HelloAck{
		Siphon: "siphon",
		Hello: "daemon",
	}); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}

	addr := daemon.launchHost()
	fmt.Printf("siphon: daemon: launched host at %s for client %s\n", addr.Label, conn.Label())

	if err := conn.Encode(siphon.Redirect{
		Addr: addr,
	}); err != nil {
		fmt.Printf("siphon: daemon: %s, dropping client %s\n", err, conn.Label())
		return
	}
}

func (daemon *Daemon) launchHost() siphon.Addr {
	for {
		siphonHostAddrStr := strings.Map(
			func(r rune) rune {
				switch r {
				case '#':
					return []rune(fmt.Sprintf("%d", rand.Int31n(9)+1))[0]
				default:
					return r
				}
			},
			daemon.opts.HostAddress,
		)

		addr, addrErr := ParseNewAddr(siphonHostAddrStr)
		if addrErr != nil {
			panic(addrErr)
		}

		fmt.Printf("siphon: daemon: attempting to launch host with address %s\n", addr.Label)
		err := daemon.attemptLaunchHost(siphonHostAddrStr)
		if err == errDaemonRetryHost {
			continue
		} else if err != nil {
			panic(err)
		} else {
			// returning a siphon.Addr struct is a bit of a clusterfuck, since we also have to be honest
			//  with ourselves that we just exec'd and passed the string form across a shell.  But would
			//  spitting that string across the network be any better?  No.  At least this way, you're
			//  only assuming that the siphon processes on the daemon side are both parsing the string
			//  the same way, and that seems ever so slightly less reckless.
			return addr
		}
	}
}

var errDaemonRetryHost = fmt.Errorf("siphon: daemon: requested host bind address already in use")

/**
 * Try to make a host at a chosen address.
 *
 * This may be rejected because the bind attempt finds the address already consumed, in which case it's
 * appropriate to simply choose a new random address and try this function again.
 */
func (daemon *Daemon) attemptLaunchHost(addr string) error {
	args := []string{
		"host",
		"-L", addr,
		"-c", daemon.opts.HostCommand,
	}
	cmd := exec.Command("siphon", args...)

	if err := cmd.Start(); err != nil {
		panic(err)
	}

	exitCode := -1
	exitCh := make(chan bool)
	go func() {
		defer close(exitCh)
		err := cmd.Wait()
		if exitError, ok := err.(*exec.ExitError); ok {
			if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
				exitCode = waitStatus.ExitStatus()
			} else { panic(exitError); }
		}
	}()

	// TODO: this entire wait is nonsense, and what we should really do is have the host send a message back to this daemon that parented it over stdout.
	//  But if we're going to start using stdout for business, we're going to tear a lot of shoddy logging statements out, add a non-for-humans mode, and take output format a great deal more seriously.
	select {
	case <- exitCh :
		if exitCode == 0 {
			// we probably don't actually expect the hosted process to return so quickly, so maybe this should later be some kind fo error
			return nil
		} else if exitCode == EXIT_BIND_IN_USE {
			return errDaemonRetryHost
		} else {
			return fmt.Errorf("siphon: daemon: unrecognized problem in spawning host (exit code %d)", exitCode)
		}
	case <- time.After(100 * time.Millisecond) :
		return nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/ami-GS/gQUIC/latest"
)

func main() {
	_, err := quiclatest.ListenAddr("127.0.0.1:8080")
	if err != nil {
		panic(err)
	}
	cli, err := quiclatest.DialAddr("127.0.0.1:8080")
	if err != nil {
		panic(err)
	}
	dataSize := 2000
	data := make([]byte, dataSize)
	for i := 0; i < dataSize; i++ {
		data[i] = byte(i%126) + 49
	}

	time.Sleep(500 * time.Millisecond)
	for i := 0; i < 3; i++ {
		cli.Send(data)
	}

	time.Sleep(1 * time.Second)

	cli.Close()
	time.Sleep(4 * time.Second)
}
<commit_msg>update example to include almost feature<commit_after>package main

import (
	"time"

	"github.com/ami-GS/gQUIC/latest"
)

func main() {
	_, err := quiclatest.ListenAddr("127.0.0.1:8080")
	if err != nil {
		panic(err)
	}

	cli, err := quiclatest.DialAddr("127.0.0.1:8080")
	if err != nil {
		panic(err)
	}

	dataSize := 2000
	basedata := make([]byte, dataSize)
	for i := 0; i < dataSize; i++ {
		basedata[i] = byte(i%126) + 49
	}
	time.Sleep(500 * time.Millisecond)
	for i := 0; i < 3; i++ {
		basedata = append(basedata, '@')
		go cli.Send(basedata)
	}

	time.Sleep(1 * time.Second)

	cli.Ping()
	time.Sleep(500 * time.Millisecond)

	cli.Close()
	time.Sleep(2 * time.Second)
}
<|endoftext|>