<commit_before>package config

import (
	"fmt"
	"io/ioutil"
	"net"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v2"
)

// Define Application Settings Structure
type CassabonConfig struct {
	Logging struct {
		Logdir   string // Log Directory
		Loglevel string // Level to log at.
	}
	Statsd   StatsdSettings
	Channels struct {
		DataStoreChanLen  int // Length of the DataStore channel
		IndexStoreChanLen int // Length of the Indexer channel
		GopherChanLen     int // Length of the StatGopher channel
	}
	Carbon struct {
		Listen     string // ip:port on which to listen for Carbon stats
		Protocol   string // "tcp", "udp" or "both" are acceptable
		Parameters struct {
			TCPTimeout int
			UDPTimeout int
		}
		Peers []string // All servers in the Cassabon array, as "ip:port"
	}
	API struct {
		Listen          string // HTTP API listens on this address:port
		HealthCheckFile string // Location of healthcheck file.
	}
	Cassandra CassandraSettings
	Redis     struct {
		Index RedisSettings // Settings for Redis Index
		Queue RedisSettings // Settings for Redis Queue
	}
	Rollups map[string]RollupSettings // Map of regex and rollups
}

// Definition of each rollup
type RollupSettings struct {
	Retention   []string
	Aggregation string
}

// Cassandra connection and schema information
type CassandraSettings struct {
	Hosts      []string // List of hostnames or IP addresses of Cassandra ring
	Port       string   // Cassandra port
	Keyspace   string   // Name of the Cassandra keyspace
	Strategy   string   // Replication class of the keyspace
	CreateOpts string   // CQL text for the strategy options
}

// Redis struct for redis connection information
type RedisSettings struct {
	Sentinel bool     // True if sentinel, false if standalone.
	Addr     []string // List of addresses in host:port format
	DB       int64    // Redis DB number for the index.
	Pwd      string   // Password for Redis.
	Master   string   // Master config name for sentinel settings.
}

type StatsdSettings struct {
	Host   string // Host or IP address of statsd server
	Port   string // Port that statsd server listens on
	Events struct {
		ReceiveOK struct {
			Key        string
			SampleRate float32
		}
		ReceiveFail struct {
			Key        string
			SampleRate float32
		}
	}
}

// rawCassabonConfig is the decoded YAML from the configuration file.
var rawCassabonConfig *CassabonConfig

// ReadConfigurationFile reads the contents of the specified file from disk, and unmarshals it.
func ReadConfigurationFile(configFile string) error {

	// Read the configuration file.
	yamlConfig, err := ioutil.ReadFile(configFile)
	if err == nil {
		// Unmarshal config file contents into raw config struct.
		err = yaml.Unmarshal(yamlConfig, &rawCassabonConfig)
	}
	return err
}

// LoadStartupValues populates the global config object with values that are used only once.
func LoadStartupValues() {

	// Copy in the logging configuration.
	G.Log.Logdir = rawCassabonConfig.Logging.Logdir
	G.Log.Loglevel = rawCassabonConfig.Logging.Loglevel

	// Copy in the statsd configuration.
	G.Statsd = rawCassabonConfig.Statsd

	// Copy in and sanitize the channel lengths.
	G.Channels.DataStoreChanLen = rawCassabonConfig.Channels.DataStoreChanLen
	if G.Channels.DataStoreChanLen < 10 {
		G.Channels.DataStoreChanLen = 10
	}
	if G.Channels.DataStoreChanLen > 1000 {
		G.Channels.DataStoreChanLen = 1000
	}
	G.Channels.IndexStoreChanLen = rawCassabonConfig.Channels.IndexStoreChanLen
	if G.Channels.IndexStoreChanLen < 10 {
		G.Channels.IndexStoreChanLen = 10
	}
	if G.Channels.IndexStoreChanLen > 1000 {
		G.Channels.IndexStoreChanLen = 1000
	}
	G.Channels.GopherChanLen = rawCassabonConfig.Channels.GopherChanLen
	if G.Channels.GopherChanLen < 10 {
		G.Channels.GopherChanLen = 10
	}
	if G.Channels.GopherChanLen > 1000 {
		G.Channels.GopherChanLen = 1000
	}
}

// ValidatePeerList ensures addresses are valid, and that the local address is in the peer list.
func ValidatePeerList(localHostPort string, peers []string) error {

	for _, v := range peers {
		if _, err := net.ResolveTCPAddr("tcp4", v); err != nil {
			return fmt.Errorf("Invalid host:port \"%s\" in peer list: %v", v, err)
		}
		if v == localHostPort {
			localHostPort = ""
		}
	}
	if localHostPort != "" {
		return fmt.Errorf("Local host:port %s is not in peer list: %v", localHostPort, peers)
	}

	return nil
}

// LoadRefreshableValues populates the global config objectwith values that
// take effect again on receipt of a SIGHUP.
func LoadRefreshableValues() {

	// Copy in the logging level (can be changed while running).
	G.Log.Loglevel = rawCassabonConfig.Logging.Loglevel

	// If the listen address is "0.0.0.0", replace it with the address of
	// the first non-localhost, non-IPv6 address found for this machine.
	if lh, lp, err := net.SplitHostPort(rawCassabonConfig.Carbon.Listen); err == nil {
		// Is this "0.0.0.0"?
		if net.ParseIP(lh).IsUnspecified() {
			// Enumerate all local addresses in CIDR format.
			if addr, err := net.InterfaceAddrs(); err == nil {
				for _, v := range addr {
					if ip, _, err := net.ParseCIDR(v.String()); err == nil {
						// Skip loopback and IPv6 addresses.
						if !ip.IsLoopback() && !strings.Contains(ip.String(), ":") {
							rawCassabonConfig.Carbon.Listen = net.JoinHostPort(ip.String(), lp)
							// That's the one we want, we're done.
							break
						}
					}
				}
			}
		}
	}

	// Copy in the Carbon listener configuration.
	// NOTE: If any of these change, all rollup accumulators must be flushed.
	G.Carbon.Listen = rawCassabonConfig.Carbon.Listen
	G.Carbon.Protocol = rawCassabonConfig.Carbon.Protocol
	G.Carbon.Peers = rawCassabonConfig.Carbon.Peers

	// Ensure a canonical order for Cassabon peers, as we will allocate
	// metrics paths to a peer by indexing into this array.
	sort.Strings(G.Carbon.Peers)

	// Ensure addresses are valid, and that the local address:port is in the peer list.
	if err := ValidatePeerList(G.Carbon.Listen, G.Carbon.Peers); err != nil {
		G.Log.System.LogFatal(err.Error())
	}

	// Copy in and sanitize the Carbon TCP listener timeout.
	G.Carbon.Parameters.TCPTimeout = rawCassabonConfig.Carbon.Parameters.TCPTimeout
	if G.Carbon.Parameters.TCPTimeout < 1 {
		G.Carbon.Parameters.TCPTimeout = 1
	}
	if G.Carbon.Parameters.TCPTimeout > 30 {
		G.Carbon.Parameters.TCPTimeout = 30
	}

	// Copy in and sanitize the Carbon UDP listener timeout.
	G.Carbon.Parameters.UDPTimeout = rawCassabonConfig.Carbon.Parameters.UDPTimeout
	if G.Carbon.Parameters.UDPTimeout < 1 {
		G.Carbon.Parameters.UDPTimeout = 1
	}
	if G.Carbon.Parameters.UDPTimeout > 30 {
		G.Carbon.Parameters.UDPTimeout = 30
	}

	// Copy in the API configuration values.
	G.API.Listen = rawCassabonConfig.API.Listen
	G.API.HealthCheckFile = rawCassabonConfig.API.HealthCheckFile

	// Copy in the Cassandra database connection values.
	G.Cassandra = rawCassabonConfig.Cassandra

	// Copy in the Redis database connection values.
	G.Redis.Index = rawCassabonConfig.Redis.Index
	G.Redis.Queue = rawCassabonConfig.Redis.Queue
}

// LoadRollups populates the global config object with the rollup definitions,
// which should only happen when there are no accumulated stats.
func LoadRollups() {

	// Validate, copy in and normalize the rollup definitions.
	G.RollupPriority = make([]string, 0, len(rawCassabonConfig.Rollups))
	G.Rollup = make(map[string]RollupDef)

	var method RollupMethod
	var window, retention time.Duration
	var err error
	var intRetention int64
	var rd *RollupDef
	reDuration := regexp.MustCompile("([0-9]+)([a-z])")

	// Inspect each rollup found in the configuration.
	// Note: YAML decode has already folded duplicate path expressions.
	for expression, v := range rawCassabonConfig.Rollups {

		// Validate and decode the aggregation method.
		switch strings.ToLower(v.Aggregation) {
		case "average":
			method = AVERAGE
		case "max":
			method = MAX
		case "min":
			method = MIN
		case "sum":
			method = SUM
		default:
			G.Log.System.LogWarn("Invalid aggregation method for \"%s\": %s", expression, v.Aggregation)
			continue
		}

		// Build up the rollup definitions for this regular expression.
		rd = new(RollupDef)
		rd.Method = method
		rd.Windows = make([]RollupWindow, 0)
		if expression != ROLLUP_CATCHALL {
			if re, err := regexp.Compile(expression); err == nil {
				rd.Expression = re
			} else {
				G.Log.System.LogWarn("Malformed regular expression for \"%s\": %v", expression, err)
				continue
			}
		}

		// Parse and validate each window:retention pair.
		for _, s := range v.Retention {

			// Split the value on the colon between the parts.
			couplet := strings.Split(s, ":")
			if len(couplet) != 2 {
				G.Log.System.LogWarn("Malformed definition for \"%s\": %s", expression, s)
				continue
			}

			// Convert the window to a time.Duration.
			if window, err = time.ParseDuration(couplet[0]); err != nil {
				G.Log.System.LogWarn("Malformed window for \"%s\": %s %s", expression, s, couplet[0])
				continue
			}

			// Don't permit windows shorter than 1 second.
			if window < time.Second {
				G.Log.System.LogWarn("Duration less than minimum 1 second for \"%s\": %v", expression, window)
				continue
			}

			// Convert the retention to a time.Duration (max: 720 years).
			// ParseDuration doesn't handle anything longer than hours, so do it manually.
			matches := reDuration.FindStringSubmatch(couplet[1]) // "1d" -> [ 1d 1 d ]
			if len(matches) != 3 {
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}
			if intRetention, err = strconv.ParseInt(matches[1], 10, 64); err != nil {
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}
			switch matches[2] {
			case "m":
				retention = time.Minute * time.Duration(intRetention)
			case "h":
				retention = time.Hour * time.Duration(intRetention)
			case "d":
				retention = time.Hour * 24 * time.Duration(intRetention)
			case "w":
				retention = time.Hour * 24 * 7 * time.Duration(intRetention)
			case "y":
				retention = time.Hour * 24 * 365 * time.Duration(intRetention)
			default:
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}

			// Record this table name in the master list of table names.
			table := fmt.Sprintf("rollup_%09d", uint64(retention.Seconds()))
			found := false
			for _, v := range G.RollupTables {
				if table == v {
					found = true
					break
				}
			}
			if !found {
				G.RollupTables = append(G.RollupTables, table)
			}

			// Append to the rollups for this expression.
			rd.Windows = append(rd.Windows, RollupWindow{window, retention, table})
		}

		// If any of the rollup window definitions were valid, add expression to the list.
		if len(rd.Windows) > 0 {

			// Sort windows into ascending duration order, and validate.
			sort.Sort(ByWindow(rd.Windows))
			shortestDuration := rd.Windows[0].Window
			allExactMultiples := true
			for i, v := range rd.Windows {
				if i > 0 {
					remainder := v.Window % shortestDuration
					if remainder != 0 {
						G.Log.System.LogWarn(
							"Next duration is not a multiple for \"%s\": %v %% %v remainder is %v",
							expression, v.Window, shortestDuration, remainder)
						allExactMultiples = false
					}
				}
			}

			// If all durations are exact multiples of the shortest duration, save this expression.
			if allExactMultiples {
				G.Rollup[expression] = *rd
				G.RollupPriority = append(G.RollupPriority, expression)
			} else {
				G.Log.System.LogWarn("Rollup expression rejected due to previous errors: \"%s\"", expression)
			}
		}
	}

	// Sort the path expressions into priority order.
	sort.Sort(ByPriority(G.RollupPriority))

	// Sort the table names.
	sort.Strings(G.RollupTables)
}
<commit_msg>Provide a default keyspace name<commit_after>package config

import (
	"fmt"
	"io/ioutil"
	"net"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v2"
)

// Define Application Settings Structure
type CassabonConfig struct {
	Logging struct {
		Logdir   string // Log Directory
		Loglevel string // Level to log at.
	}
	Statsd   StatsdSettings
	Channels struct {
		DataStoreChanLen  int // Length of the DataStore channel
		IndexStoreChanLen int // Length of the Indexer channel
		GopherChanLen     int // Length of the StatGopher channel
	}
	Carbon struct {
		Listen     string // ip:port on which to listen for Carbon stats
		Protocol   string // "tcp", "udp" or "both" are acceptable
		Parameters struct {
			TCPTimeout int
			UDPTimeout int
		}
		Peers []string // All servers in the Cassabon array, as "ip:port"
	}
	API struct {
		Listen          string // HTTP API listens on this address:port
		HealthCheckFile string // Location of healthcheck file.
	}
	Cassandra CassandraSettings
	Redis     struct {
		Index RedisSettings // Settings for Redis Index
		Queue RedisSettings // Settings for Redis Queue
	}
	Rollups map[string]RollupSettings // Map of regex and rollups
}

// Definition of each rollup
type RollupSettings struct {
	Retention   []string
	Aggregation string
}

// Cassandra connection and schema information
type CassandraSettings struct {
	Hosts      []string // List of hostnames or IP addresses of Cassandra ring
	Port       string   // Cassandra port
	Keyspace   string   // Name of the Cassandra keyspace
	Strategy   string   // Replication class of the keyspace
	CreateOpts string   // CQL text for the strategy options
}

// Redis struct for redis connection information
type RedisSettings struct {
	Sentinel bool     // True if sentinel, false if standalone.
	Addr     []string // List of addresses in host:port format
	DB       int64    // Redis DB number for the index.
	Pwd      string   // Password for Redis.
	Master   string   // Master config name for sentinel settings.
}

type StatsdSettings struct {
	Host   string // Host or IP address of statsd server
	Port   string // Port that statsd server listens on
	Events struct {
		ReceiveOK struct {
			Key        string
			SampleRate float32
		}
		ReceiveFail struct {
			Key        string
			SampleRate float32
		}
	}
}

// rawCassabonConfig is the decoded YAML from the configuration file.
var rawCassabonConfig *CassabonConfig

// ReadConfigurationFile reads the contents of the specified file from disk, and unmarshals it.
func ReadConfigurationFile(configFile string) error {

	// Read the configuration file.
	yamlConfig, err := ioutil.ReadFile(configFile)
	if err == nil {
		// Unmarshal config file contents into raw config struct.
		err = yaml.Unmarshal(yamlConfig, &rawCassabonConfig)
	}
	return err
}

// LoadStartupValues populates the global config object with values that are used only once.
func LoadStartupValues() {

	// Copy in the logging configuration.
	G.Log.Logdir = rawCassabonConfig.Logging.Logdir
	G.Log.Loglevel = rawCassabonConfig.Logging.Loglevel

	// Copy in the statsd configuration.
	G.Statsd = rawCassabonConfig.Statsd

	// Copy in and sanitize the channel lengths.
	G.Channels.DataStoreChanLen = rawCassabonConfig.Channels.DataStoreChanLen
	if G.Channels.DataStoreChanLen < 10 {
		G.Channels.DataStoreChanLen = 10
	}
	if G.Channels.DataStoreChanLen > 1000 {
		G.Channels.DataStoreChanLen = 1000
	}
	G.Channels.IndexStoreChanLen = rawCassabonConfig.Channels.IndexStoreChanLen
	if G.Channels.IndexStoreChanLen < 10 {
		G.Channels.IndexStoreChanLen = 10
	}
	if G.Channels.IndexStoreChanLen > 1000 {
		G.Channels.IndexStoreChanLen = 1000
	}
	G.Channels.GopherChanLen = rawCassabonConfig.Channels.GopherChanLen
	if G.Channels.GopherChanLen < 10 {
		G.Channels.GopherChanLen = 10
	}
	if G.Channels.GopherChanLen > 1000 {
		G.Channels.GopherChanLen = 1000
	}
}

// ValidatePeerList ensures addresses are valid, and that the local address is in the peer list.
func ValidatePeerList(localHostPort string, peers []string) error {

	for _, v := range peers {
		if _, err := net.ResolveTCPAddr("tcp4", v); err != nil {
			return fmt.Errorf("Invalid host:port \"%s\" in peer list: %v", v, err)
		}
		if v == localHostPort {
			localHostPort = ""
		}
	}
	if localHostPort != "" {
		return fmt.Errorf("Local host:port %s is not in peer list: %v", localHostPort, peers)
	}

	return nil
}

// LoadRefreshableValues populates the global config objectwith values that
// take effect again on receipt of a SIGHUP.
func LoadRefreshableValues() {

	// Copy in the logging level (can be changed while running).
	G.Log.Loglevel = rawCassabonConfig.Logging.Loglevel

	// If the listen address is "0.0.0.0", replace it with the address of
	// the first non-localhost, non-IPv6 address found for this machine.
	if lh, lp, err := net.SplitHostPort(rawCassabonConfig.Carbon.Listen); err == nil {
		// Is this "0.0.0.0"?
		if net.ParseIP(lh).IsUnspecified() {
			// Enumerate all local addresses in CIDR format.
			if addr, err := net.InterfaceAddrs(); err == nil {
				for _, v := range addr {
					if ip, _, err := net.ParseCIDR(v.String()); err == nil {
						// Skip loopback and IPv6 addresses.
						if !ip.IsLoopback() && !strings.Contains(ip.String(), ":") {
							rawCassabonConfig.Carbon.Listen = net.JoinHostPort(ip.String(), lp)
							// That's the one we want, we're done.
							break
						}
					}
				}
			}
		}
	}

	// Copy in the Carbon listener configuration.
	// NOTE: If any of these change, all rollup accumulators must be flushed.
	G.Carbon.Listen = rawCassabonConfig.Carbon.Listen
	G.Carbon.Protocol = rawCassabonConfig.Carbon.Protocol
	G.Carbon.Peers = rawCassabonConfig.Carbon.Peers

	// Ensure a canonical order for Cassabon peers, as we will allocate
	// metrics paths to a peer by indexing into this array.
	sort.Strings(G.Carbon.Peers)

	// Ensure addresses are valid, and that the local address:port is in the peer list.
	if err := ValidatePeerList(G.Carbon.Listen, G.Carbon.Peers); err != nil {
		G.Log.System.LogFatal(err.Error())
	}

	// Copy in and sanitize the Carbon TCP listener timeout.
	G.Carbon.Parameters.TCPTimeout = rawCassabonConfig.Carbon.Parameters.TCPTimeout
	if G.Carbon.Parameters.TCPTimeout < 1 {
		G.Carbon.Parameters.TCPTimeout = 1
	}
	if G.Carbon.Parameters.TCPTimeout > 30 {
		G.Carbon.Parameters.TCPTimeout = 30
	}

	// Copy in and sanitize the Carbon UDP listener timeout.
	G.Carbon.Parameters.UDPTimeout = rawCassabonConfig.Carbon.Parameters.UDPTimeout
	if G.Carbon.Parameters.UDPTimeout < 1 {
		G.Carbon.Parameters.UDPTimeout = 1
	}
	if G.Carbon.Parameters.UDPTimeout > 30 {
		G.Carbon.Parameters.UDPTimeout = 30
	}

	// Copy in the API configuration values.
	G.API.Listen = rawCassabonConfig.API.Listen
	G.API.HealthCheckFile = rawCassabonConfig.API.HealthCheckFile

	// Copy in the Cassandra database connection values.
	G.Cassandra = rawCassabonConfig.Cassandra
	if G.Cassandra.Keyspace == "" {
		G.Cassandra.Keyspace = "cassabon"
	}

	// Copy in the Redis database connection values.
	G.Redis.Index = rawCassabonConfig.Redis.Index
	G.Redis.Queue = rawCassabonConfig.Redis.Queue
}

// LoadRollups populates the global config object with the rollup definitions,
// which should only happen when there are no accumulated stats.
func LoadRollups() {

	// Validate, copy in and normalize the rollup definitions.
	G.RollupPriority = make([]string, 0, len(rawCassabonConfig.Rollups))
	G.Rollup = make(map[string]RollupDef)

	var method RollupMethod
	var window, retention time.Duration
	var err error
	var intRetention int64
	var rd *RollupDef
	reDuration := regexp.MustCompile("([0-9]+)([a-z])")

	// Inspect each rollup found in the configuration.
	// Note: YAML decode has already folded duplicate path expressions.
	for expression, v := range rawCassabonConfig.Rollups {

		// Validate and decode the aggregation method.
		switch strings.ToLower(v.Aggregation) {
		case "average":
			method = AVERAGE
		case "max":
			method = MAX
		case "min":
			method = MIN
		case "sum":
			method = SUM
		default:
			G.Log.System.LogWarn("Invalid aggregation method for \"%s\": %s", expression, v.Aggregation)
			continue
		}

		// Build up the rollup definitions for this regular expression.
		rd = new(RollupDef)
		rd.Method = method
		rd.Windows = make([]RollupWindow, 0)
		if expression != ROLLUP_CATCHALL {
			if re, err := regexp.Compile(expression); err == nil {
				rd.Expression = re
			} else {
				G.Log.System.LogWarn("Malformed regular expression for \"%s\": %v", expression, err)
				continue
			}
		}

		// Parse and validate each window:retention pair.
		for _, s := range v.Retention {

			// Split the value on the colon between the parts.
			couplet := strings.Split(s, ":")
			if len(couplet) != 2 {
				G.Log.System.LogWarn("Malformed definition for \"%s\": %s", expression, s)
				continue
			}

			// Convert the window to a time.Duration.
			if window, err = time.ParseDuration(couplet[0]); err != nil {
				G.Log.System.LogWarn("Malformed window for \"%s\": %s %s", expression, s, couplet[0])
				continue
			}

			// Don't permit windows shorter than 1 second.
			if window < time.Second {
				G.Log.System.LogWarn("Duration less than minimum 1 second for \"%s\": %v", expression, window)
				continue
			}

			// Convert the retention to a time.Duration (max: 720 years).
			// ParseDuration doesn't handle anything longer than hours, so do it manually.
			matches := reDuration.FindStringSubmatch(couplet[1]) // "1d" -> [ 1d 1 d ]
			if len(matches) != 3 {
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}
			if intRetention, err = strconv.ParseInt(matches[1], 10, 64); err != nil {
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}
			switch matches[2] {
			case "m":
				retention = time.Minute * time.Duration(intRetention)
			case "h":
				retention = time.Hour * time.Duration(intRetention)
			case "d":
				retention = time.Hour * 24 * time.Duration(intRetention)
			case "w":
				retention = time.Hour * 24 * 7 * time.Duration(intRetention)
			case "y":
				retention = time.Hour * 24 * 365 * time.Duration(intRetention)
			default:
				G.Log.System.LogWarn("Malformed retention for \"%s\": %s %s", expression, s, couplet[1])
				continue
			}

			// Record this table name in the master list of table names.
			table := fmt.Sprintf("rollup_%09d", uint64(retention.Seconds()))
			found := false
			for _, v := range G.RollupTables {
				if table == v {
					found = true
					break
				}
			}
			if !found {
				G.RollupTables = append(G.RollupTables, table)
			}

			// Append to the rollups for this expression.
			rd.Windows = append(rd.Windows, RollupWindow{window, retention, table})
		}

		// If any of the rollup window definitions were valid, add expression to the list.
		if len(rd.Windows) > 0 {

			// Sort windows into ascending duration order, and validate.
			sort.Sort(ByWindow(rd.Windows))
			shortestDuration := rd.Windows[0].Window
			allExactMultiples := true
			for i, v := range rd.Windows {
				if i > 0 {
					remainder := v.Window % shortestDuration
					if remainder != 0 {
						G.Log.System.LogWarn(
							"Next duration is not a multiple for \"%s\": %v %% %v remainder is %v",
							expression, v.Window, shortestDuration, remainder)
						allExactMultiples = false
					}
				}
			}

			// If all durations are exact multiples of the shortest duration, save this expression.
			if allExactMultiples {
				G.Rollup[expression] = *rd
				G.RollupPriority = append(G.RollupPriority, expression)
			} else {
				G.Log.System.LogWarn("Rollup expression rejected due to previous errors: \"%s\"", expression)
			}
		}
	}

	// Sort the path expressions into priority order.
	sort.Sort(ByPriority(G.RollupPriority))

	// Sort the table names.
	sort.Strings(G.RollupTables)
}
<|endoftext|>


<commit_before>package app

import termbox "github.com/nsf/termbox-go"

type dfScreenEventHandler struct {
	baseEventHandler
}

func (h *dfScreenEventHandler) handle(event termbox.Event) {
	h.setFocus(true)
}
<commit_msg>Add event handler for df screen<commit_after>package app

import (
	"github.com/moncho/dry/appui"
	termbox "github.com/nsf/termbox-go"
)

const (
	confirmation = `WARNING! This will remove all unused data. Are you sure you want to continue? [y/N]`
)

type diskUsageScreenEventHandler struct {
	baseEventHandler
}

func (h *diskUsageScreenEventHandler) handle(event termbox.Event) {
	handled := false
	ignored := false
	switch event.Key {
	case termbox.KeyArrowUp | termbox.KeyArrowDown:
		//To avoid that the base handler handles this
		ignored = true
		handled = true

	case termbox.KeyCtrlP: //prune
		handled = true
		if confirmation, err := appui.ReadLine(confirmation); err == nil {
			h.screen.ClearAndFlush()
			if confirmation == "Y" || confirmation == "y" {
				h.dry.Prune()
			}
		}
	}
	if handled {
		h.setFocus(true)
		if !ignored {
			h.renderChan <- struct{}{}
		}
	} else {
		h.baseEventHandler.handle(event)
	}

}
<|endoftext|>


<commit_before>package gofigure

import (
	"github.com/droundy/goopt"
	"log"
	"os"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	Version				string
	options				map[string]*Option
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DisableCommandLine:	false,
		EnvOverridesFile:   false,
		options:			make(map[string]*Option),
		flags:				make(map[string]*string),
		values:				make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:   name,
		envVar: "",
		def:    "",
		desc:   "",
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+name)
		c.flags[name] = GooptFigureString(cmdline, o.def, o.desc)
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			*c.values[name] = val
			passed[name] = true
		}
	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.Name(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if (c.FileParser == nil) {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if p, ok := c.values[k]; ok {
			*p = v
			passed[k] = true
		}
	}
	return nil
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name, envVar, shortOpt, def, desc string
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) Name() string {
	return o.name
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<commit_msg>added the ability to change the long option name<commit_after>package gofigure

import (
	"github.com/droundy/goopt"
	"log"
	"os"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	Version				string
	options				map[string]*Option
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DisableCommandLine:	false,
		EnvOverridesFile:   false,
		options:			make(map[string]*Option),
		flags:				make(map[string]*string),
		values:				make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+o.longOpt)
		c.flags[name] = GooptFigureString(cmdline, o.def, o.desc)
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			*c.values[name] = val
			passed[name] = true
		}
	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.Name(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if (c.FileParser == nil) {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if p, ok := c.values[k]; ok {
			*p = v
			passed[k] = true
		}
	}
	return nil
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name, envVar, shortOpt, def, desc, longOpt string
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) Name() string {
	return o.name
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

func (o *Option) LongOpt(opt string) *Option {
	o.longOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"strings"

	"github.com/google/uuid"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerInfo struct {
	Kind    peerKind
	Name    string
	ID      string
	Channel chan string
}

const peerIDParamName string = "peer_id"
const toParamName string = "to"

const peerMessageBufferSize int = 100

var peers = make(map[string]peerInfo)

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc func(http.ResponseWriter, *http.Request)) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.HandleFunc(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func signinHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan string, peerMessageBufferSize)

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	uuid, err := uuid.NewRandom()
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}

	peerInfo.ID = uuid.String()

	peers[peerInfo.ID] = peerInfo

	peerInfoString := fmt.Sprintf("%s,%s,1", peerInfo.Name, peerInfo.ID)
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind {
			responseString += fmt.Sprintf("%s,%s,1", pInfo.Name, pInfo.ID)
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- peerInfoString
			} else {
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	fmt.Fprintf(res, responseString)
	// http.Error(res, "Not implemented "+name+" "+uuid.String(), http.statusadd)
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())
	var peerID string
	// Parse out peers id
	for k, v := range req.URL.Query() {
		if k == peerIDParamName {
			peerID = v[0]
		}
	}
	_, exists := peers[peerID]
	if !exists {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerID, peerExists := req.URL.Query()[peerIDParamName]
	toID, toExists := req.URL.Query()[toParamName]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	_, peerInfoExists := peers[peerID[0]]
	to, toInfoExists := peers[toID[0]]

	if !peerInfoExists || !toInfoExists {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Invalid Peer or To ID", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- requestString

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerID, peerExists := req.URL.Query()[peerIDParamName]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerInfo, peerInfoExists := peers[peerID[0]]

	if !peerInfoExists {
		http.Error(res, "Peer is backed up", http.StatusBadRequest)
		return
	}

	// Look up message channel for peers id
	// Wait for message to reply
	responseString := <-peerInfo.Channel

	res.WriteHeader(http.StatusOK)
	fmt.Fprint(res, responseString)
}

func main() {
	fmt.Println("gosigsrv starting")
	fmt.Println()
	// Register handlers
	registerHandler("/sign_in", signinHandler)
	registerHandler("/sign_out", signoutHandler)
	registerHandler("/message", messageHandler)
	registerHandler("/wait", waitHandler)
	registerHandler("/", printReqHandler)

	// Start listening
	http.ListenAndServe(":8087", nil)
	fmt.Println()
	fmt.Println("gosigsrv exiting")
}
<commit_msg>Allow port to be config by env var 'PORT'<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"strings"

	"github.com/google/uuid"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerInfo struct {
	Kind    peerKind
	Name    string
	ID      string
	Channel chan string
}

const peerIDParamName string = "peer_id"
const toParamName string = "to"

const peerMessageBufferSize int = 100

var peers = make(map[string]peerInfo)

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc func(http.ResponseWriter, *http.Request)) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.HandleFunc(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func signinHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan string, peerMessageBufferSize)

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	uuid, err := uuid.NewRandom()
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}

	peerInfo.ID = uuid.String()

	peers[peerInfo.ID] = peerInfo

	peerInfoString := fmt.Sprintf("%s,%s,1", peerInfo.Name, peerInfo.ID)
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind {
			responseString += fmt.Sprintf("%s,%s,1", pInfo.Name, pInfo.ID)
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- peerInfoString
			} else {
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	fmt.Fprintf(res, responseString)
	// http.Error(res, "Not implemented "+name+" "+uuid.String(), http.statusadd)
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())
	var peerID string
	// Parse out peers id
	for k, v := range req.URL.Query() {
		if k == peerIDParamName {
			peerID = v[0]
		}
	}
	_, exists := peers[peerID]
	if !exists {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerID, peerExists := req.URL.Query()[peerIDParamName]
	toID, toExists := req.URL.Query()[toParamName]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	_, peerInfoExists := peers[peerID[0]]
	to, toInfoExists := peers[toID[0]]

	if !peerInfoExists || !toInfoExists {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Invalid Peer or To ID", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- requestString

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerID, peerExists := req.URL.Query()[peerIDParamName]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerInfo, peerInfoExists := peers[peerID[0]]

	if !peerInfoExists {
		http.Error(res, "Peer is backed up", http.StatusBadRequest)
		return
	}

	// Look up message channel for peers id
	// Wait for message to reply
	responseString := <-peerInfo.Channel

	res.WriteHeader(http.StatusOK)
	fmt.Fprint(res, responseString)
}

func main() {

	fmt.Println("gosigsrv starting")
	fmt.Println()

	port := os.Getenv("PORT")
	if port == "" {
		port = "8087"
	}

	fmt.Printf("Will listen on port %s\n\n", port)

	// Register handlers
	registerHandler("/sign_in", signinHandler)
	registerHandler("/sign_out", signoutHandler)
	registerHandler("/message", messageHandler)
	registerHandler("/wait", waitHandler)
	registerHandler("/", printReqHandler)

	// Start listening
	http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
	fmt.Println()
	fmt.Println("gosigsrv exiting")
}
<|endoftext|>


<commit_before>package gows

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/gows/config"
)

// PrepareEnvironmentAndRunCommand ...
// Returns the exit code of the command and any error occured in the function
func PrepareEnvironmentAndRunCommand(cmdName string, cmdArgs ...string) (int, error) {
	projectConfig, err := config.LoadProjectConfigFromFile()
	if err != nil {
		log.Info("Run " + colorstring.Green("gows init") + " to initialize a workspace & gows config for this project")
		return 0, fmt.Errorf("Failed to read Project Config: %s", err)
	}

	gowsConfig, err := config.LoadGOWSConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read gows configs: %s", err)
	}
	currWorkDir, err := os.Getwd()
	if err != nil {
		return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to get current working directory: %s", err)
	}

	wsConfig, isFound := gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	if !isFound {
		log.Debugln("No initialized workspace dir found for this project, initializing one ...")
		if err := initWorkspaceForProjectPath(currWorkDir, false); err != nil {
			return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to initialize Workspace for Project: %s", err)
		}
		log.Debugln("[DONE] workspace dir initialized - continue running ...")

		// reload config
		gowsConfig, err := config.LoadGOWSConfigFromFile()
		if err != nil {
			return 0, fmt.Errorf("Failed to read gows configs: %s", err)
		}
		wsConfig, isFound = gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	}
	if !isFound {
		log.Info("Run " + colorstring.Green("gows init") + " to initialize a workspace & gows config for this project")
		return 0, fmt.Errorf("No Workspace configuration found for the current project / working directory: %s", currWorkDir)
	}

	userConfig, err := config.LoadUserConfigFromFile()
	if err != nil {
		log.Debug("No User Config found, using defaults")
		userConfig = config.CreateDefaultUserConfig()
	}
	log.Debugf("Using User Config: %#v", userConfig)

	origGOPATH := os.Getenv("GOPATH")
	if origGOPATH == "" {
		return 0, fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked")
	}

	if wsConfig.WorkspaceRootPath == "" {
		return 0, fmt.Errorf("No gows Workspace root path found for the current project / working directory: %s", currWorkDir)
	}
	if projectConfig.PackageName == "" {
		return 0, errors.New("No Package Name specified - make sure you initialized the workspace (with: gows init)")
	}

	if err := pathutil.EnsureDirExist(wsConfig.WorkspaceRootPath); err != nil {
		return 0, fmt.Errorf("Failed to create workspace root directory (path: %s), error: %s", wsConfig.WorkspaceRootPath, err)
	}

	if err := createGopathBinSymlink(origGOPATH, wsConfig); err != nil {
		return 0, fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	}

	fullPackageWorkspacePath := filepath.Join(wsConfig.WorkspaceRootPath, "src", projectConfig.PackageName)

	userConfigSyncMode := userConfig.SyncMode
	if userConfigSyncMode == "" {
		userConfigSyncMode = config.DefaultSyncMode
	}
	log.Debug("[PrepareEnvironmentAndRunCommand] specified Sync Mode : ", userConfigSyncMode)

	// prepare
	{
		fullPackageWorkspacePathFileInfo, fullPackageWorkspaceIsExists, err := pathutil.PathCheckAndInfos(fullPackageWorkspacePath)
		if err != nil {
			return 0, fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", fullPackageWorkspacePath, err)
		}

		switch userConfigSyncMode {
		case config.SyncModeSymlink:
			// create symlink for Project->Workspace
			if fullPackageWorkspaceIsExists && fullPackageWorkspacePathFileInfo.Mode()&os.ModeSymlink == 0 {
				// directory (non symlink) exists - remove it
				log.Warningf("Directory exists (at: %s)", fullPackageWorkspacePath)
				log.Warning("Removing it ...")
				if err := os.RemoveAll(fullPackageWorkspacePath); err != nil {
					return 0, fmt.Errorf("Failed to remove Directory (at: %s), error: %s", fullPackageWorkspacePath, err)
				}
			}

			log.Debugf("=> Creating Symlink: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
			if err := createOrUpdateSymlink(currWorkDir, fullPackageWorkspacePath); err != nil {
				return 0, fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err)
			}
			log.Debugf(" [DONE] Symlink is in place")
		case config.SyncModeCopy:
			// Sync project into workspace
			if fullPackageWorkspaceIsExists && fullPackageWorkspacePathFileInfo.Mode()&os.ModeSymlink != 0 {
				// symlink exists - remove it
				log.Warningf("Symlink exists (at: %s)", fullPackageWorkspacePath)
				log.Warning("Removing it ...")
				if err := os.Remove(fullPackageWorkspacePath); err != nil {
					return 0, fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", fullPackageWorkspacePath, err)
				}
			}

			log.Debugf("=> Sync project content into workspace: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
			if err := syncDirWithDir(currWorkDir, fullPackageWorkspacePath); err != nil {
				return 0, fmt.Errorf("Failed to sync the project path / workdir into the Workspace, error: %s", err)
			}
			log.Debugf(" [DONE] Sync project content into workspace")
		default:
			return 0, fmt.Errorf("Unsupported Sync Mode: %s", userConfigSyncMode)
		}
	}

	// Run the command, in the prepared Workspace
	exitCode, cmdErr := runCommand(origGOPATH, fullPackageWorkspacePath, wsConfig, cmdName, cmdArgs...)

	// cleanup / finishing
	{
		switch userConfigSyncMode {
		case config.SyncModeSymlink:
			// nothing to do
		case config.SyncModeCopy:
			// Sync back from workspace into project
			log.Debugf("=> Sync workspace content into project: (%s) -> (%s)", fullPackageWorkspacePath, currWorkDir)
			if err := syncDirWithDir(fullPackageWorkspacePath, currWorkDir); err != nil {
				// we should return the command's exit code and error (if any)
				// maybe if the exitCode==0 and cmdErr==nil only then we could return an error here ...
				// for now we'll just print an error log, but it won't change the "output" of this function
				log.Errorf("Failed to sync back the project content from the Workspace, error: %s", err)
			} else {
				log.Debugf(" [DONE] Sync back project content from workspace")
			}
		default:
			return 0, fmt.Errorf("Unsupported Sync Mode: %s", userConfigSyncMode)
		}
	}

	return exitCode, cmdErr
}

func syncDirWithDir(syncContentOf, syncIntoDir string) error {
	syncContentOf = filepath.Clean(syncContentOf)
	syncIntoDir = filepath.Clean(syncIntoDir)

	if err := pathutil.EnsureDirExist(syncIntoDir); err != nil {
		return fmt.Errorf("Failed to create target (at: %s), error: %s", syncIntoDir, err)
	}

	cmd := exec.Command("rsync", "-avhP", "--delete", syncContentOf+"/", syncIntoDir+"/")
	cmd.Stdin = os.Stdin

	log.Debugf("[syncDirWithDir] Running command: $ %s", cmdex.PrintableCommandArgs(false, cmd.Args))
	out, err := cmd.Output()
	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			log.Error("[syncDirWithDir] Sync Error")
			log.Errorf("[syncDirWithDir] Output (Stdout) was: %s", out)
			log.Errorf("[syncDirWithDir] Error Output (Stderr) was: %s", exitError.Stderr)
		} else {
			log.Error("[syncDirWithDir] Failed to convert error to ExitError")
		}
		return fmt.Errorf("Failed to rsync between (%s) and (%s), error: %s", syncContentOf, syncIntoDir, err)
	}
	return nil
}

func createOrUpdateSymlink(symlinkTargetPath, symlinkLocationPath string) error {
	fileInfo, isExists, err := pathutil.PathCheckAndInfos(symlinkLocationPath)
	if err != nil {
		return fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", symlinkLocationPath, err)
	}
	isSymlinkAlreadyInPlace := false
	if isExists && fileInfo.Mode()&os.ModeSymlink != 0 {
		log.Debug(" Symlink already exists")
		originPth, err := os.Readlink(symlinkLocationPath)
		if err != nil {
			return fmt.Errorf("Symlink found (at: %s), but failed to open: %s", symlinkLocationPath, err)
		}

		if originPth == symlinkTargetPath {
			isSymlinkAlreadyInPlace = true
		} else {
			// remove
			log.Warningf("Symlink already exists (at: %s), but target (%s) is not the current one (%s)", symlinkLocationPath, originPth, symlinkTargetPath)
			log.Warning("Removing and re-creating the symlink ...")
			if err := os.Remove(symlinkLocationPath); err != nil {
				return fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", symlinkLocationPath, err)
			}
		}
	}

	if !isSymlinkAlreadyInPlace {
		log.Debug(" Creating symlink ...")
		// create the parent directory
		if err := os.MkdirAll(filepath.Dir(symlinkLocationPath), 0777); err != nil {
			return fmt.Errorf("Failed to create base directory for symlink into: %s", symlinkLocationPath)
		}
		// create symlink
		if err := os.Symlink(symlinkTargetPath, symlinkLocationPath); err != nil {
			return fmt.Errorf("Failed to create symlink from project directory (%s) into gows Workspace directory (%s), error: %s", symlinkTargetPath, symlinkLocationPath, err)
		}
	}

	return nil
}

func createGopathBinSymlink(origGOPATH string, wsConfig config.WorkspaceConfigModel) error {
	fullWorkspaceBinPath := filepath.Join(wsConfig.WorkspaceRootPath, "bin")
	originalGopathBinPath, err := pathutil.AbsPath(filepath.Join(origGOPATH, "bin"))
	if err != nil {
		return fmt.Errorf("Failed to get the path of 'bin' dir inside your GOPATH (%s), error: %s", origGOPATH, err)
	}

	log.Debugf("=> Creating Symlink: (%s) -> (%s)", originalGopathBinPath, fullWorkspaceBinPath)

	// create symlink for GOPATH/bin, if not yet created
	if err := createOrUpdateSymlink(originalGopathBinPath, fullWorkspaceBinPath); err != nil {
		return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	}

	log.Debugf(" [DONE] Symlink is in place")

	return nil
}

func filteredEnvsList(envsList []string, ignoreEnv string) []string {
	filteredEnvs := []string{}
	for _, envItem := range envsList {
		// an env item is a single string with the syntax: key=the value
		if !strings.HasPrefix(envItem, fmt.Sprintf("%s=", ignoreEnv)) {
			filteredEnvs = append(filteredEnvs, envItem)
		}
	}
	return filteredEnvs
}

// runCommand runs the command with it's arguments
// Returns the exit code of the command and any error occured in the function
func runCommand(originalGOPATH, cmdWorkdir string, wsConfig config.WorkspaceConfigModel, cmdName string, cmdArgs ...string) (int, error) {
	log.Debugf("[RunCommand] Command Name: %s", cmdName)
	log.Debugf("[RunCommand] Command Args: %#v", cmdArgs)
	log.Debugf("[RunCommand] Command Work Dir: %#v", cmdWorkdir)

	cmd := exec.Command(cmdName, cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = cmdWorkdir
	//
	cmdEnvs := os.Environ()
	cmdEnvs = filteredEnvsList(cmdEnvs, "GOPATH")
	cmdEnvs = filteredEnvsList(cmdEnvs, "PWD")
	cmdEnvs = append(cmdEnvs,
		fmt.Sprintf("GOPATH=%s", wsConfig.WorkspaceRootPath),
		fmt.Sprintf("PWD=%s", cmdWorkdir),
	)
	cmd.Env = cmdEnvs

	cmdExitCode := 0
	if err := cmd.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			waitStatus, ok := exitError.Sys().(syscall.WaitStatus)
			if !ok {
				return 0, errors.New("Failed to cast exit status")
			}
			cmdExitCode = waitStatus.ExitStatus()
		}
		return cmdExitCode, err
	}

	return 0, nil
}
<commit_msg>gows-copy-mode-active<commit_after>package gows

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/bitrise-io/go-utils/fileutil"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/gows/config"
)

const (
	gowsCopyModeActiveFilePath = "./GOWS-COPY-MODE-ACTIVE"
)

// PrepareEnvironmentAndRunCommand ...
// Returns the exit code of the command and any error occured in the function
func PrepareEnvironmentAndRunCommand(cmdName string, cmdArgs ...string) (int, error) {
	projectConfig, err := config.LoadProjectConfigFromFile()
	if err != nil {
		log.Info("Run " + colorstring.Green("gows init") + " to initialize a workspace & gows config for this project")
		return 0, fmt.Errorf("Failed to read Project Config: %s", err)
	}

	gowsConfig, err := config.LoadGOWSConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read gows configs: %s", err)
	}
	currWorkDir, err := os.Getwd()
	if err != nil {
		return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to get current working directory: %s", err)
	}

	wsConfig, isFound := gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	if !isFound {
		log.Debugln("No initialized workspace dir found for this project, initializing one ...")
		if err := initWorkspaceForProjectPath(currWorkDir, false); err != nil {
			return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to initialize Workspace for Project: %s", err)
		}
		log.Debugln("[DONE] workspace dir initialized - continue running ...")

		// reload config
		gowsConfig, err := config.LoadGOWSConfigFromFile()
		if err != nil {
			return 0, fmt.Errorf("Failed to read gows configs: %s", err)
		}
		wsConfig, isFound = gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	}
	if !isFound {
		log.Info("Run " + colorstring.Green("gows init") + " to initialize a workspace & gows config for this project")
		return 0, fmt.Errorf("No Workspace configuration found for the current project / working directory: %s", currWorkDir)
	}

	userConfig, err := config.LoadUserConfigFromFile()
	if err != nil {
		log.Debug("No User Config found, using defaults")
		userConfig = config.CreateDefaultUserConfig()
	}
	log.Debugf("Using User Config: %#v", userConfig)

	origGOPATH := os.Getenv("GOPATH")
	if origGOPATH == "" {
		return 0, fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked")
	}

	if wsConfig.WorkspaceRootPath == "" {
		return 0, fmt.Errorf("No gows Workspace root path found for the current project / working directory: %s", currWorkDir)
	}
	if projectConfig.PackageName == "" {
		return 0, errors.New("No Package Name specified - make sure you initialized the workspace (with: gows init)")
	}

	if err := pathutil.EnsureDirExist(wsConfig.WorkspaceRootPath); err != nil {
		return 0, fmt.Errorf("Failed to create workspace root directory (path: %s), error: %s", wsConfig.WorkspaceRootPath, err)
	}

	if err := createGopathBinSymlink(origGOPATH, wsConfig); err != nil {
		return 0, fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	}

	fullPackageWorkspacePath := filepath.Join(wsConfig.WorkspaceRootPath, "src", projectConfig.PackageName)

	userConfigSyncMode := userConfig.SyncMode
	if userConfigSyncMode == "" {
		userConfigSyncMode = config.DefaultSyncMode
	}
	log.Debug("[PrepareEnvironmentAndRunCommand] specified Sync Mode : ", userConfigSyncMode)

	// --- prepare ---
	{
		fullPackageWorkspacePathFileInfo, fullPackageWorkspaceIsExists, err := pathutil.PathCheckAndInfos(fullPackageWorkspacePath)
		if err != nil {
			return 0, fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", fullPackageWorkspacePath, err)
		}

		switch userConfigSyncMode {
		case config.SyncModeSymlink:
			// create symlink for Project->Workspace
			if fullPackageWorkspaceIsExists && fullPackageWorkspacePathFileInfo.Mode()&os.ModeSymlink == 0 {
				// directory (non symlink) exists - remove it
				log.Warningf("Directory exists (at: %s)", fullPackageWorkspacePath)
				log.Warning("Removing it ...")
				if err := os.RemoveAll(fullPackageWorkspacePath); err != nil {
					return 0, fmt.Errorf("Failed to remove Directory (at: %s), error: %s", fullPackageWorkspacePath, err)
				}
			}

			log.Debugf("=> Creating Symlink: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
			if err := createOrUpdateSymlink(currWorkDir, fullPackageWorkspacePath); err != nil {
				return 0, fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err)
			}
			log.Debugf(" [DONE] Symlink is in place")
		case config.SyncModeCopy:
			// Sync project into workspace
			if fullPackageWorkspaceIsExists && fullPackageWorkspacePathFileInfo.Mode()&os.ModeSymlink != 0 {
				// symlink exists - remove it
				log.Warningf("Symlink exists (at: %s)", fullPackageWorkspacePath)
				log.Warning("Removing it ...")
				if err := os.Remove(fullPackageWorkspacePath); err != nil {
					return 0, fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", fullPackageWorkspacePath, err)
				}
			}

			log.Debugf("=> Sync project content into workspace: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
			if err := syncDirWithDir(currWorkDir, fullPackageWorkspacePath); err != nil {
				return 0, fmt.Errorf("Failed to sync the project path / workdir into the Workspace, error: %s", err)
			}
			if err := writeGowsCopySyncActiveFileToPath(gowsCopyModeActiveFilePath, fullPackageWorkspacePath, currWorkDir); err != nil {
				log.Warningf(" [!] Failed to write gows-copy-mode-active file to path: %s", gowsCopyModeActiveFilePath)
			}
			log.Debugf(" [DONE] Sync project content into workspace")
		default:
			return 0, fmt.Errorf("Unsupported Sync Mode: %s", userConfigSyncMode)
		}
	}

	// Run the command, in the prepared Workspace
	exitCode, cmdErr := runCommand(origGOPATH, fullPackageWorkspacePath, wsConfig, cmdName, cmdArgs...)

	// cleanup / finishing
	{
		switch userConfigSyncMode {
		case config.SyncModeSymlink:
			// nothing to do
		case config.SyncModeCopy:
			// Sync back from workspace into project
			log.Debugf("=> Sync workspace content into project: (%s) -> (%s)", fullPackageWorkspacePath, currWorkDir)
			if err := syncDirWithDir(fullPackageWorkspacePath, currWorkDir); err != nil {
				// we should return the command's exit code and error (if any)
				// maybe if the exitCode==0 and cmdErr==nil only then we could return an error here ...
				// for now we'll just print an error log, but it won't change the "output" of this function
				log.Errorf("Failed to sync back the project content from the Workspace, error: %s", err)
			} else {
				log.Debugf(" [DONE] Sync back project content from workspace")
			}
		default:
			return 0, fmt.Errorf("Unsupported Sync Mode: %s", userConfigSyncMode)
		}
	}

	return exitCode, cmdErr
}

func writeGowsCopySyncActiveFileToPath(pth, gowsWorkspacePath, originalProjectPath string) error {
	gowsCopyModeActiveContent := fmt.Sprintf(`gows workspace is active at the path: %s

Changes you do here (%s) WILL NOT SYNC, and WILL BE OVERWRITTEN by the changes done
inside the workspace (at path: %s) when sync/the current command is finished!

This file will be removed after the sync-back. After that it's safe to work
in this directory again.
`,
		gowsWorkspacePath, originalProjectPath, gowsWorkspacePath)

	return fileutil.WriteStringToFile(pth, gowsCopyModeActiveContent)
}

func syncDirWithDir(syncContentOf, syncIntoDir string) error {
	syncContentOf = filepath.Clean(syncContentOf)
	syncIntoDir = filepath.Clean(syncIntoDir)

	if err := pathutil.EnsureDirExist(syncIntoDir); err != nil {
		return fmt.Errorf("Failed to create target (at: %s), error: %s", syncIntoDir, err)
	}

	cmd := exec.Command("rsync", "-avhP", "--delete", syncContentOf+"/", syncIntoDir+"/")
	cmd.Stdin = os.Stdin

	log.Debugf("[syncDirWithDir] Running command: $ %s", cmdex.PrintableCommandArgs(false, cmd.Args))
	out, err := cmd.Output()
	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			log.Error("[syncDirWithDir] Sync Error")
			log.Errorf("[syncDirWithDir] Output (Stdout) was: %s", out)
			log.Errorf("[syncDirWithDir] Error Output (Stderr) was: %s", exitError.Stderr)
		} else {
			log.Error("[syncDirWithDir] Failed to convert error to ExitError")
		}
		return fmt.Errorf("Failed to rsync between (%s) and (%s), error: %s", syncContentOf, syncIntoDir, err)
	}
	return nil
}

func createOrUpdateSymlink(symlinkTargetPath, symlinkLocationPath string) error {
	fileInfo, isExists, err := pathutil.PathCheckAndInfos(symlinkLocationPath)
	if err != nil {
		return fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", symlinkLocationPath, err)
	}
	isSymlinkAlreadyInPlace := false
	if isExists && fileInfo.Mode()&os.ModeSymlink != 0 {
		log.Debug(" Symlink already exists")
		originPth, err := os.Readlink(symlinkLocationPath)
		if err != nil {
			return fmt.Errorf("Symlink found (at: %s), but failed to open: %s", symlinkLocationPath, err)
		}

		if originPth == symlinkTargetPath {
			isSymlinkAlreadyInPlace = true
		} else {
			// remove
			log.Warningf("Symlink already exists (at: %s), but target (%s) is not the current one (%s)", symlinkLocationPath, originPth, symlinkTargetPath)
			log.Warning("Removing and re-creating the symlink ...")
			if err := os.Remove(symlinkLocationPath); err != nil {
				return fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", symlinkLocationPath, err)
			}
		}
	}

	if !isSymlinkAlreadyInPlace {
		log.Debug(" Creating symlink ...")
		// create the parent directory
		if err := os.MkdirAll(filepath.Dir(symlinkLocationPath), 0777); err != nil {
			return fmt.Errorf("Failed to create base directory for symlink into: %s", symlinkLocationPath)
		}
		// create symlink
		if err := os.Symlink(symlinkTargetPath, symlinkLocationPath); err != nil {
			return fmt.Errorf("Failed to create symlink from project directory (%s) into gows Workspace directory (%s), error: %s", symlinkTargetPath, symlinkLocationPath, err)
		}
	}

	return nil
}

func createGopathBinSymlink(origGOPATH string, wsConfig config.WorkspaceConfigModel) error {
	fullWorkspaceBinPath := filepath.Join(wsConfig.WorkspaceRootPath, "bin")
	originalGopathBinPath, err := pathutil.AbsPath(filepath.Join(origGOPATH, "bin"))
	if err != nil {
		return fmt.Errorf("Failed to get the path of 'bin' dir inside your GOPATH (%s), error: %s", origGOPATH, err)
	}

	log.Debugf("=> Creating Symlink: (%s) -> (%s)", originalGopathBinPath, fullWorkspaceBinPath)

	// create symlink for GOPATH/bin, if not yet created
	if err := createOrUpdateSymlink(originalGopathBinPath, fullWorkspaceBinPath); err != nil {
		return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	}

	log.Debugf(" [DONE] Symlink is in place")

	return nil
}

func filteredEnvsList(envsList []string, ignoreEnv string) []string {
	filteredEnvs := []string{}
	for _, envItem := range envsList {
		// an env item is a single string with the syntax: key=the value
		if !strings.HasPrefix(envItem, fmt.Sprintf("%s=", ignoreEnv)) {
			filteredEnvs = append(filteredEnvs, envItem)
		}
	}
	return filteredEnvs
}

// runCommand runs the command with it's arguments
// Returns the exit code of the command and any error occured in the function
func runCommand(originalGOPATH, cmdWorkdir string, wsConfig config.WorkspaceConfigModel, cmdName string, cmdArgs ...string) (int, error) {
	log.Debugf("[RunCommand] Command Name: %s", cmdName)
	log.Debugf("[RunCommand] Command Args: %#v", cmdArgs)
	log.Debugf("[RunCommand] Command Work Dir: %#v", cmdWorkdir)

	cmd := exec.Command(cmdName, cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = cmdWorkdir
	//
	cmdEnvs := os.Environ()
	cmdEnvs = filteredEnvsList(cmdEnvs, "GOPATH")
	cmdEnvs = filteredEnvsList(cmdEnvs, "PWD")
	cmdEnvs = append(cmdEnvs,
		fmt.Sprintf("GOPATH=%s", wsConfig.WorkspaceRootPath),
		fmt.Sprintf("PWD=%s", cmdWorkdir),
	)
	cmd.Env = cmdEnvs

	cmdExitCode := 0
	if err := cmd.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			waitStatus, ok := exitError.Sys().(syscall.WaitStatus)
			if !ok {
				return 0, errors.New("Failed to cast exit status")
			}
			cmdExitCode = waitStatus.ExitStatus()
		}
		return cmdExitCode, err
	}

	return 0, nil
}
<|endoftext|>


<commit_before>/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

    * Neither the name of "The Computer Language Benchmarks Game" nor the
    name of "The Computer Language Shootout Benchmarks" nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

/* The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * contributed by The Go Authors.
 * Based on mandelbrot.c contributed by Greg Buchholz
 */

package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
)

var n = flag.Int("n", 200, "size")

func main() {
	flag.Parse()
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()

	w := *n
	h := *n
	bit_num := 0
	byte_acc := byte(0)
	const Iter = 50
	const Zero float64 = 0
	const Limit = 2.0

	fmt.Fprintf(out, "P4\n%d %d\n", w, h)

	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			Zr, Zi, Tr, Ti := Zero, Zero, Zero, Zero
			Cr := (2*float64(x)/float64(w) - 1.5)
			Ci := (2*float64(y)/float64(h) - 1.0)

			for i := 0; i < Iter && (Tr+Ti <= Limit*Limit); i++ {
				Zi = 2*Zr*Zi + Ci
				Zr = Tr - Ti + Cr
				Tr = Zr * Zr
				Ti = Zi * Zi
			}

			byte_acc <<= 1
			if Tr+Ti <= Limit*Limit {
				byte_acc |= 0x01
			}

			bit_num++

			if bit_num == 8 {
				out.WriteByte(byte_acc)
				byte_acc = 0
				bit_num = 0
			} else if x == w-1 {
				byte_acc <<= uint(8 - w%8)
				out.WriteByte(byte_acc)
				byte_acc = 0
				bit_num = 0
			}
		}
	}
}
<commit_msg>shootout: make mandelbrot.go more like mandelbrot.c<commit_after>/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

    * Neither the name of "The Computer Language Benchmarks Game" nor the
    name of "The Computer Language Shootout Benchmarks" nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

/* The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * contributed by The Go Authors.
 * Based on mandelbrot.c contributed by Greg Buchholz
 */

package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
)

var n = flag.Int("n", 200, "size")

func main() {
	flag.Parse()
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()

	w := float64(*n)
	h := float64(*n)
	bit_num := 0
	byte_acc := byte(0)
	const Iter = 50
	const Zero float64 = 0
	const Limit = 2.0

	fmt.Fprintf(out, "P4\n%d %d\n", *n, *n)

	for y := 0.0; y < h; y++ {
		for x := 0.0; x < w; x++ {
			Zr, Zi, Tr, Ti := Zero, Zero, Zero, Zero
			Cr := (2*x/w - 1.5)
			Ci := (2*y/h - 1.0)

			for i := 0; i < Iter && (Tr+Ti <= Limit*Limit); i++ {
				Zi = 2*Zr*Zi + Ci
				Zr = Tr - Ti + Cr
				Tr = Zr * Zr
				Ti = Zi * Zi
			}

			byte_acc <<= 1
			if Tr+Ti <= Limit*Limit {
				byte_acc |= 0x01
			}

			bit_num++

			if bit_num == 8 {
				out.WriteByte(byte_acc)
				byte_acc = 0
				bit_num = 0
			} else if x == w-1 {
				byte_acc <<= uint(8 - uint(*n)%8)
				out.WriteByte(byte_acc)
				byte_acc = 0
				bit_num = 0
			}
		}
	}
}
<|endoftext|>


<commit_before>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package modules

import (
	"encoding/json"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

type Module interface {
	Name() string
	Help() string
	Load(*irc.Client) error
	Defaults()
}

type ModuleSet struct {
	client  *irc.Client
	modules []Module
	configs string
}

func NewModuleSet(client *irc.Client, configs string) *ModuleSet {
	return &ModuleSet{client: client, configs: configs}
}

func (m *ModuleSet) Register(module Module) {
	m.modules = append(m.modules, module)
}

func (m *ModuleSet) LoadAll() error {
	if err := os.MkdirAll(m.configs, 0755); err != nil {
		return err
	}

	for _, module := range m.modules {
		fn := fmt.Sprintf("%s.json", module.Name())
		fp := filepath.Join(m.configs, fn)

		module.Defaults()
		data, err := ioutil.ReadFile(fp)
		if err == nil {
			if err := json.Unmarshal(data, &module); err != nil {
				return err
			}
		} else if !os.IsNotExist(err) {
			return err
		}

		if err := module.Load(m.client); err != nil {
			return err
		}
	}

	m.client.CmdHook("privmsg", m.helpCmd)
	m.client.CmdHook("privmsg", m.moduleCmd)

	return nil
}

func (m *ModuleSet) findModule(name string) Module {
	for _, module := range m.modules {
		if module.Name() == name {
			return module
		}
	}

	return nil
}

func (m *ModuleSet) helpCmd(client *irc.Client, msg irc.Message) error {
	if msg.Data != "!help" && len(m.modules) >= 1 {
		return nil
	}

	var names []string
	for _, module := range m.modules {
		names = append(names, module.Name())
	}

	help := fmt.Sprintf("The following modules are available: %s",
		strings.Join(names, ", "))

	return client.Write("NOTICE %s :%s", msg.Receiver, help)
}

func (m *ModuleSet) moduleCmd(client *irc.Client, msg irc.Message) error {
	splited := strings.Fields(msg.Data)
	if len(splited) < 2 || splited[0] != "!help" {
		return nil
	}

	name := strings.ToLower(splited[1])
	module := m.findModule(name)
	if module == nil {
		return client.Write("NOTICE %s :Module %q isn't installed",
			msg.Receiver, name)
	}

	return client.Write("NOTICE %s :%s: %s",
		msg.Receiver, module.Name(), module.Help())
}
<commit_msg>Don't show help if no modules are available<commit_after>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package modules

import (
	"encoding/json"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

type Module interface {
	Name() string
	Help() string
	Load(*irc.Client) error
	Defaults()
}

type ModuleSet struct {
	client  *irc.Client
	modules []Module
	configs string
}

func NewModuleSet(client *irc.Client, configs string) *ModuleSet {
	return &ModuleSet{client: client, configs: configs}
}

func (m *ModuleSet) Register(module Module) {
	m.modules = append(m.modules, module)
}

func (m *ModuleSet) LoadAll() error {
	if err := os.MkdirAll(m.configs, 0755); err != nil {
		return err
	}

	for _, module := range m.modules {
		fn := fmt.Sprintf("%s.json", module.Name())
		fp := filepath.Join(m.configs, fn)

		module.Defaults()
		data, err := ioutil.ReadFile(fp)
		if err == nil {
			if err := json.Unmarshal(data, &module); err != nil {
				return err
			}
		} else if !os.IsNotExist(err) {
			return err
		}

		if err := module.Load(m.client); err != nil {
			return err
		}
	}

	m.client.CmdHook("privmsg", m.helpCmd)
	m.client.CmdHook("privmsg", m.moduleCmd)

	return nil
}

func (m *ModuleSet) findModule(name string) Module {
	for _, module := range m.modules {
		if module.Name() == name {
			return module
		}
	}

	return nil
}

func (m *ModuleSet) helpCmd(client *irc.Client, msg irc.Message) error {
	if msg.Data != "!help" || len(m.modules) <= 0 {
		return nil
	}

	var names []string
	for _, module := range m.modules {
		names = append(names, module.Name())
	}

	help := fmt.Sprintf("The following modules are available: %s",
		strings.Join(names, ", "))

	return client.Write("NOTICE %s :%s", msg.Receiver, help)
}

func (m *ModuleSet) moduleCmd(client *irc.Client, msg irc.Message) error {
	splited := strings.Fields(msg.Data)
	if len(splited) < 2 || splited[0] != "!help" {
		return nil
	}

	name := strings.ToLower(splited[1])
	module := m.findModule(name)
	if module == nil {
		return client.Write("NOTICE %s :Module %q isn't installed",
			msg.Receiver, name)
	}

	return client.Write("NOTICE %s :%s: %s",
		msg.Receiver, module.Name(), module.Help())
}
<|endoftext|>


<commit_before>/*
 Copyright (c) 2014 Kouhei Maeda <mkouhei@palmtb.net>

 This software is release under the Expat License.
*/
package modules

import (
	"github.com/ajstarks/svgo"
	"math/rand"
)

func (canv Canvas) Network() *svg.SVG {
	canvas := svg.New(canv.W)
	canvas.Start(canv.Width, canv.Height)

	d := 5 // diameter
	linestyle := "fill:none;stroke:black"
	circlestyle := "fill:gray"

	nd := 511
	xpoly := make([]int, nd)
	ypoly := make([]int, nd)
	for i := 0; i < nd; i++ {
		xpoly[i] = rand.Intn(canv.Width)
		ypoly[i] = rand.Intn(canv.Height)
		canvas.Circle(xpoly[i], ypoly[i], d, circlestyle)
		if i > 0 {
			canvas.Line(xpoly[i-1], ypoly[i-1], xpoly[i], ypoly[i], linestyle)
		}
	}
	//canvas.Polyline(xpoly, ypoly, linestyle)
	/*
		for i := 0; i < nd; i++ {
			xpoly[i] = rand.Intn(canv.Width)
			ypoly[i] = rand.Intn(canv.Height)
			canvas.Circle(xpoly[i], ypoly[i], d, circlestyle)
		}
		canvas.Polyline(xpoly, ypoly, linestyle)
	*/

	canvas.End()
	return canvas
}
<commit_msg>Added description.<commit_after>/*
 Copyright (c) 2014 Kouhei Maeda <mkouhei@palmtb.net>

 This software is release under the Expat License.
*/
package modules

import (
	"github.com/ajstarks/svgo"
	"math/rand"
	"strconv"
)

func (canv Canvas) Network() *svg.SVG {
	canvas := svg.New(canv.W)
	canvas.Start(canv.Width, canv.Height)

	d := 5 // diameter
	linestyle := "fill:none;stroke:black"
	textstyle := "font-size:10px"
	circlestyle := "fill:gray"

	nd := 100
	xpoly := make([]int, nd)
	ypoly := make([]int, nd)
	for i := 0; i < nd; i++ {
		xpoly[i] = rand.Intn(canv.Width)
		ypoly[i] = rand.Intn(canv.Height)
		canvas.Circle(xpoly[i], ypoly[i], d, circlestyle)
		canvas.Text(xpoly[i]+d*2, ypoly[i]-d*2, "("+strconv.Itoa(xpoly[i])+","+strconv.Itoa(ypoly[i])+")", textstyle)
		if i > 0 {
			canvas.Line(xpoly[i-1], ypoly[i-1], xpoly[i], ypoly[i], linestyle)
		}
	}
	//canvas.Polyline(xpoly, ypoly, linestyle)
	/*
		for i := 0; i < nd; i++ {
			xpoly[i] = rand.Intn(canv.Width)
			ypoly[i] = rand.Intn(canv.Height)
			canvas.Circle(xpoly[i], ypoly[i], d, circlestyle)
		}
		canvas.Polyline(xpoly, ypoly, linestyle)
	*/

	canvas.End()
	return canvas
}
<|endoftext|>


<commit_before>package infrastructure

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	httpurl "net/url"
	"time"
)

type Client struct {
	url    *httpurl.URL
	Client *http.Client
}

func New(url string) (*Client, error) {
	u, err := httpurl.Parse(url)
	if err != nil {
		return nil, err
	}
	return &Client{
		url:    u,
		Client: &http.Client{},
	}, nil
}

func NewFromURL(url httpurl.URL) *Client {
	return &Client{&url, &http.Client{}}
}

type TimeInterval struct {
	From time.Time
	To   time.Time
}

func (t *TimeInterval) Check() error {
	if t.From.After(t.To) {
		return errors.New("From must be before To.")
	}
	return nil
}

func graphiteDateFormat(t time.Time) string {
	return fmt.Sprintf("%02d:%02d_%d%02d%02d", t.Hour(), t.Minute(), t.Year(), t.Month(), t.Day())
}

type FloatDatapoint struct {
	Time  time.Time
	Value *float64
}

type IntDatapoint struct {
	Time  time.Time
	Value *int64
}

type Datapoints struct {
	// Previous error to make API nicer.
	err    error
	points [][]interface{}
}

func (d Datapoints) AsInts() ([]IntDatapoint, error) {
	if d.err != nil {
		return nil, d.err
	}

	points := make([]IntDatapoint, 0, len(d.points))
	for _, point := range d.points {
		jsonUnixTime, ok := point[1].(json.Number)
		if !ok {
			return nil, errors.New("Unix timestamp not number.")
		}
		unixTime, err := jsonUnixTime.Int64()
		if err != nil {
			return nil, errors.New("Unix time not proper number.")
		}

		var value *int64
		if point[0] != nil {
			jsonValue, ok := point[0].(json.Number)
			if !ok {
				return nil, errors.New("Value not a number.")
			}
			value = new(int64)
			*value, err = jsonValue.Int64()
			if err != nil {
				return nil, errors.New("Value not proper number.")
			}
		}
		points = append(points, IntDatapoint{time.Unix(unixTime, 0), value})
	}

	return points, nil
}

func (d Datapoints) AsFloats() ([]FloatDatapoint, error) {
	if d.err != nil {
		return nil, d.err
	}

	points := make([]FloatDatapoint, 0, len(d.points))
	for _, point := range d.points {
		jsonUnixTime, ok := point[1].(json.Number)
		if !ok {
			return nil, errors.New("Unix timestamp not number.")
		}
		unixTime, err := jsonUnixTime.Int64()
		if err != nil {
			return nil, errors.New("Unix time not proper number.")
		}

		var value *float64
		if point[0] != nil {
			jsonValue, ok := point[0].(json.Number)
			if !ok {
				return nil, errors.New("Value not a number.")
			}
			value = new(float64)
			*value, err = jsonValue.Float64()
			if err != nil {
				return nil, errors.New("Value not proper number.")
			}
		}
		points = append(points, FloatDatapoint{time.Unix(unixTime, 0), value})
	}

	return points, nil
}

func constructQueryPart(q string) httpurl.Values {
	query := make(httpurl.Values)
	query.Add("target", q)
	query.Add("format", "json")
	return query
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryInts(q string, interval TimeInterval) ([]IntDatapoint, error) {
	return g.Query(q, interval).AsInts()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryFloats(q string, interval TimeInterval) ([]FloatDatapoint, error) {
	return g.Query(q, interval).AsFloats()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryIntsSince(q string, ago time.Duration) ([]IntDatapoint, error) {
	return g.QuerySince(q, ago).AsInts()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryFloatsSince(q string, ago time.Duration) ([]FloatDatapoint, error) {
	return g.QuerySince(q, ago).AsFloats()
}

// Fetches a Graphite result. Deferring identifying whether the result are ints
// of floats to later. Useful in clients that executes adhoc queries.
func (g *Client) Query(q string, interval TimeInterval) Datapoints {
	if err := interval.Check(); err != nil {
		return Datapoints{err, nil}
	}

	// Cloning to be able to modify.
	url := g.url

	queryPart := constructQueryPart(q)
	queryPart.Add("from", graphiteDateFormat(interval.From))
	queryPart.Add("until", graphiteDateFormat(interval.To))
	url.RawQuery = queryPart.Encode()

	resp, err := g.Client.Get(url.String())
	if err != nil {
		return Datapoints{err, nil}
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Datapoints{err, nil}
	}

	return parseGraphiteResponse(body)
}

func (g *Client) QuerySince(q string, ago time.Duration) Datapoints {
	if ago.Nanoseconds() > 0 {
		return Datapoints{errors.New("Duration is expected to be positive."), nil}
	}

	// Cloning to be able to modify.
	url := g.url

	queryPart := constructQueryPart(q)
	queryPart.Add("from", fmt.Sprintf("%dminutes", ago.Minutes()))
	url.RawQuery = queryPart.Encode()

	resp, err := http.Get(url.String())
	if err != nil {
		return Datapoints{err, nil}
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Datapoints{err, nil}
	}

	return parseGraphiteResponse(body)
}

func parseGraphiteResponse(body []byte) Datapoints {
	var dps Datapoints
	var res []target

	decoder := json.NewDecoder(bytes.NewBuffer(body))

	// Important to distinguish between ints and floats.
	decoder.UseNumber()

	dps.err = decoder.Decode(&res)
	if len(res) == 0 {
		dps.err = errors.New("Unexpected Graphite response. No targets were matched.")
	}
	if len(res) > 1 {
		dps.err = errors.New("Unexpected Graphite response. More than one target was returned.")
	}
	if dps.err != nil {
		return dps
	}

	// TODO: Check the query is the same as the target name.

	dps.points = res[0].Datapoints
	return dps
}

type queryResult []target

type target struct {
	Target string `json:"target"`

	// Datapoints are either
	//
	//     [[FLOAT, INT], ..., [FLOAT, INT]]  (type []intDatapoint).
	//
	// or
	//
	//     [[FLOAT, FLOAT], ..., [FLOAT, FLOAT]] (type []floatDatapoint).
	Datapoints [][]interface{} `json:"datapoints"`
}
<commit_msg>fix: grammar in error message<commit_after>package infrastructure

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	httpurl "net/url"
	"time"
)

type Client struct {
	url    *httpurl.URL
	Client *http.Client
}

func New(url string) (*Client, error) {
	u, err := httpurl.Parse(url)
	if err != nil {
		return nil, err
	}
	return &Client{
		url:    u,
		Client: &http.Client{},
	}, nil
}

func NewFromURL(url httpurl.URL) *Client {
	return &Client{&url, &http.Client{}}
}

type TimeInterval struct {
	From time.Time
	To   time.Time
}

func (t *TimeInterval) Check() error {
	if t.From.After(t.To) {
		return errors.New("From must be before To.")
	}
	return nil
}

func graphiteDateFormat(t time.Time) string {
	return fmt.Sprintf("%02d:%02d_%d%02d%02d", t.Hour(), t.Minute(), t.Year(), t.Month(), t.Day())
}

type FloatDatapoint struct {
	Time  time.Time
	Value *float64
}

type IntDatapoint struct {
	Time  time.Time
	Value *int64
}

type Datapoints struct {
	// Previous error to make API nicer.
	err    error
	points [][]interface{}
}

func (d Datapoints) AsInts() ([]IntDatapoint, error) {
	if d.err != nil {
		return nil, d.err
	}

	points := make([]IntDatapoint, 0, len(d.points))
	for _, point := range d.points {
		jsonUnixTime, ok := point[1].(json.Number)
		if !ok {
			return nil, errors.New("Unix timestamp not number.")
		}
		unixTime, err := jsonUnixTime.Int64()
		if err != nil {
			return nil, errors.New("Unix time not proper number.")
		}

		var value *int64
		if point[0] != nil {
			jsonValue, ok := point[0].(json.Number)
			if !ok {
				return nil, errors.New("Value not a number.")
			}
			value = new(int64)
			*value, err = jsonValue.Int64()
			if err != nil {
				return nil, errors.New("Value not proper number.")
			}
		}
		points = append(points, IntDatapoint{time.Unix(unixTime, 0), value})
	}

	return points, nil
}

func (d Datapoints) AsFloats() ([]FloatDatapoint, error) {
	if d.err != nil {
		return nil, d.err
	}

	points := make([]FloatDatapoint, 0, len(d.points))
	for _, point := range d.points {
		jsonUnixTime, ok := point[1].(json.Number)
		if !ok {
			return nil, errors.New("Unix timestamp not number.")
		}
		unixTime, err := jsonUnixTime.Int64()
		if err != nil {
			return nil, errors.New("Unix time not proper number.")
		}

		var value *float64
		if point[0] != nil {
			jsonValue, ok := point[0].(json.Number)
			if !ok {
				return nil, errors.New("Value not a number.")
			}
			value = new(float64)
			*value, err = jsonValue.Float64()
			if err != nil {
				return nil, errors.New("Value not proper number.")
			}
		}
		points = append(points, FloatDatapoint{time.Unix(unixTime, 0), value})
	}

	return points, nil
}

func constructQueryPart(q string) httpurl.Values {
	query := make(httpurl.Values)
	query.Add("target", q)
	query.Add("format", "json")
	return query
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryInts(q string, interval TimeInterval) ([]IntDatapoint, error) {
	return g.Query(q, interval).AsInts()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryFloats(q string, interval TimeInterval) ([]FloatDatapoint, error) {
	return g.Query(q, interval).AsFloats()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryIntsSince(q string, ago time.Duration) ([]IntDatapoint, error) {
	return g.QuerySince(q, ago).AsInts()
}

// Helper method to make it easier to create an interface for Client.
func (g *Client) QueryFloatsSince(q string, ago time.Duration) ([]FloatDatapoint, error) {
	return g.QuerySince(q, ago).AsFloats()
}

// Fetches a Graphite result. Deferring identifying whether the result are ints
// of floats to later. Useful in clients that executes adhoc queries.
func (g *Client) Query(q string, interval TimeInterval) Datapoints {
	if err := interval.Check(); err != nil {
		return Datapoints{err, nil}
	}

	// Cloning to be able to modify.
	url := g.url

	queryPart := constructQueryPart(q)
	queryPart.Add("from", graphiteDateFormat(interval.From))
	queryPart.Add("until", graphiteDateFormat(interval.To))
	url.RawQuery = queryPart.Encode()

	resp, err := g.Client.Get(url.String())
	if err != nil {
		return Datapoints{err, nil}
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Datapoints{err, nil}
	}

	return parseGraphiteResponse(body)
}

func (g *Client) QuerySince(q string, ago time.Duration) Datapoints {
	if ago.Nanoseconds() > 0 {
		return Datapoints{errors.New("Duration is expected to be positive."), nil}
	}

	// Cloning to be able to modify.
	url := g.url

	queryPart := constructQueryPart(q)
	queryPart.Add("from", fmt.Sprintf("%dminutes", ago.Minutes()))
	url.RawQuery = queryPart.Encode()

	resp, err := http.Get(url.String())
	if err != nil {
		return Datapoints{err, nil}
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Datapoints{err, nil}
	}

	return parseGraphiteResponse(body)
}

func parseGraphiteResponse(body []byte) Datapoints {
	var dps Datapoints
	var res []target

	decoder := json.NewDecoder(bytes.NewBuffer(body))

	// Important to distinguish between ints and floats.
	decoder.UseNumber()

	dps.err = decoder.Decode(&res)
	if len(res) == 0 {
		dps.err = errors.New("Unexpected Graphite response. No targets were matched.")
	}
	if len(res) > 1 {
		dps.err = errors.New("Unexpected Graphite response. More than one target were returned.")
	}
	if dps.err != nil {
		return dps
	}

	// TODO: Check the query is the same as the target name.

	dps.points = res[0].Datapoints
	return dps
}

type queryResult []target

type target struct {
	Target string `json:"target"`

	// Datapoints are either
	//
	//     [[FLOAT, INT], ..., [FLOAT, INT]]  (type []intDatapoint).
	//
	// or
	//
	//     [[FLOAT, FLOAT], ..., [FLOAT, FLOAT]] (type []floatDatapoint).
	Datapoints [][]interface{} `json:"datapoints"`
}
<|endoftext|>