<commit_before>// Copyright 2012-2019 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"time"
)

// Command is a signal used to control a running nats-server process.
type Command string

// Valid Command values.
const (
	CommandStop   = Command("stop")
	CommandQuit   = Command("quit")
	CommandReopen = Command("reopen")
	CommandReload = Command("reload")

	// private for now
	commandLDMode = Command("ldm")
)

var (
	// gitCommit injected at build
	gitCommit string
	// trustedKeys is a whitespace separated array of trusted operator's public nkeys.
	trustedKeys string
)

const (
	// VERSION is the current version for the server.
	VERSION = "2.1.3-RC02"

	// PROTO is the currently supported protocol.
	// 0 was the original
	// 1 maintains proto 0, adds echo abilities for CONNECT from the client. Clients
	// should not send echo unless proto in INFO is >= 1.
	PROTO = 1

	// DEFAULT_PORT is the default port for client connections.
	DEFAULT_PORT = 4222

	// RANDOM_PORT is the value for port that, when supplied, will cause the
	// server to listen on a randomly-chosen available port. The resolved port
	// is available via the Addr() method.
	RANDOM_PORT = -1

	// DEFAULT_HOST defaults to all interfaces.
	DEFAULT_HOST = "0.0.0.0"

	// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
	// 4k should be plenty since payloads sans connect/info string are separate.
	MAX_CONTROL_LINE_SIZE = 4096

	// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
	// something different if > 1MB payloads are needed.
	MAX_PAYLOAD_SIZE = (1024 * 1024)

	// MAX_PENDING_SIZE is the maximum outbound pending bytes per client.
	MAX_PENDING_SIZE = (64 * 1024 * 1024)

	// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
	DEFAULT_MAX_CONNECTIONS = (64 * 1024)

	// TLS_TIMEOUT is the TLS wait time.
	TLS_TIMEOUT = 500 * time.Millisecond

	// AUTH_TIMEOUT is the authorization wait time.
	AUTH_TIMEOUT = 2 * TLS_TIMEOUT

	// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
	DEFAULT_PING_INTERVAL = 2 * time.Minute

	// DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect.
	DEFAULT_PING_MAX_OUT = 2

	// CR_LF string
	CR_LF = "\r\n"

	// LEN_CR_LF hold onto the computed size.
	LEN_CR_LF = len(CR_LF)

	// DEFAULT_FLUSH_DEADLINE is the write/flush deadlines.
	DEFAULT_FLUSH_DEADLINE = 2 * time.Second

	// DEFAULT_HTTP_PORT is the default monitoring port.
	DEFAULT_HTTP_PORT = 8222

	// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
	ACCEPT_MIN_SLEEP = 10 * time.Millisecond

	// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
	ACCEPT_MAX_SLEEP = 1 * time.Second

	// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
	DEFAULT_ROUTE_CONNECT = 1 * time.Second

	// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
	DEFAULT_ROUTE_RECONNECT = 1 * time.Second

	// DEFAULT_ROUTE_DIAL Route dial timeout.
	DEFAULT_ROUTE_DIAL = 1 * time.Second

	// DEFAULT_LEAF_NODE_RECONNECT LeafNode reconnect interval.
	DEFAULT_LEAF_NODE_RECONNECT = time.Second

	// DEFAULT_LEAF_TLS_TIMEOUT TLS timeout for LeafNodes
	DEFAULT_LEAF_TLS_TIMEOUT = 2 * time.Second

	// PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors.
	PROTO_SNIPPET_SIZE = 32

	// MAX_MSG_ARGS Maximum possible number of arguments from MSG proto.
	MAX_MSG_ARGS = 4

	// MAX_PUB_ARGS Maximum possible number of arguments from PUB proto.
	MAX_PUB_ARGS = 3

	// DEFAULT_MAX_CLOSED_CLIENTS is the maximum number of closed connections we hold onto.
	DEFAULT_MAX_CLOSED_CLIENTS = 10000

	// DEFAULT_MAX_ACCOUNT_AE_RESPONSE_MAPS is for auto-expire response maps for imports.
	DEFAULT_MAX_ACCOUNT_AE_RESPONSE_MAPS = 100000

	// DEFAULT_MAX_ACCOUNT_INTERNAL_RESPONSE_MAPS is for non auto-expire response maps for imports.
	// These are present for non-singleton response types.
	DEFAULT_MAX_ACCOUNT_INTERNAL_RESPONSE_MAPS = 100000

	// DEFAULT_TTL_AE_RESPONSE_MAP is the default time to expire auto-response map entries.
	DEFAULT_TTL_AE_RESPONSE_MAP = 10 * time.Minute

	// DEFAULT_LAME_DUCK_DURATION is the time in which the server spreads
	// the closing of clients when signaled to go in lame duck mode.
	DEFAULT_LAME_DUCK_DURATION = 2 * time.Minute

	// DEFAULT_LEAFNODE_INFO_WAIT Route dial timeout.
	DEFAULT_LEAFNODE_INFO_WAIT = 1 * time.Second

	// DEFAULT_LEAFNODE_PORT is the default port for remote leafnode connections.
	DEFAULT_LEAFNODE_PORT = 7422

	// DEFAULT_CONNECT_ERROR_REPORTS is the number of attempts at which a
	// repeated failed route, gateway or leaf node connection is reported.
	// This is used for initial connection, that is, when the server has
	// never had a connection to the given endpoint. Once connected, and
	// if a disconnect occurs, DEFAULT_RECONNECT_ERROR_REPORTS is used
	// instead.
	// The default is to report every 3600 attempts (roughly every hour).
	DEFAULT_CONNECT_ERROR_REPORTS = 3600

	// DEFAULT_RECONNECT_ERROR_REPORTS is the default number of failed
	// attempt to reconnect a route, gateway or leaf node connection.
	// The default is to report every attempt.
	DEFAULT_RECONNECT_ERROR_REPORTS = 1

	// DEFAULT_RTT_MEASUREMENT_INTERVAL is how often we want to measure RTT from
	// this server to clients, routes, gateways or leafnode connections.
	DEFAULT_RTT_MEASUREMENT_INTERVAL = time.Hour

	// DEFAULT_ALLOW_RESPONSE_MAX_MSGS is the default number of responses allowed
	// for a reply subject.
	DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1

	// DEFAULT_ALLOW_RESPONSE_EXPIRATION is the default time allowed for a given
	// dynamic response permission.
	DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2 * time.Minute

	// DEFAULT_SERVICE_LATENCY_SAMPLING is the default sampling rate for service
	// latency metrics
	DEFAULT_SERVICE_LATENCY_SAMPLING = 100
)
<commit_msg>Bump to version 2.1.3-RC03<commit_after>// Copyright 2012-2019 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"time"
)

// Command is a signal used to control a running nats-server process.
type Command string

// Valid Command values.
const (
	CommandStop   = Command("stop")
	CommandQuit   = Command("quit")
	CommandReopen = Command("reopen")
	CommandReload = Command("reload")

	// private for now
	commandLDMode = Command("ldm")
)

var (
	// gitCommit injected at build
	gitCommit string
	// trustedKeys is a whitespace separated array of trusted operator's public nkeys.
	trustedKeys string
)

const (
	// VERSION is the current version for the server.
	VERSION = "2.1.3-RC03"

	// PROTO is the currently supported protocol.
	// 0 was the original
	// 1 maintains proto 0, adds echo abilities for CONNECT from the client. Clients
	// should not send echo unless proto in INFO is >= 1.
	PROTO = 1

	// DEFAULT_PORT is the default port for client connections.
	DEFAULT_PORT = 4222

	// RANDOM_PORT is the value for port that, when supplied, will cause the
	// server to listen on a randomly-chosen available port. The resolved port
	// is available via the Addr() method.
	RANDOM_PORT = -1

	// DEFAULT_HOST defaults to all interfaces.
	DEFAULT_HOST = "0.0.0.0"

	// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
	// 4k should be plenty since payloads sans connect/info string are separate.
	MAX_CONTROL_LINE_SIZE = 4096

	// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
	// something different if > 1MB payloads are needed.
	MAX_PAYLOAD_SIZE = (1024 * 1024)

	// MAX_PENDING_SIZE is the maximum outbound pending bytes per client.
	MAX_PENDING_SIZE = (64 * 1024 * 1024)

	// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
	DEFAULT_MAX_CONNECTIONS = (64 * 1024)

	// TLS_TIMEOUT is the TLS wait time.
	TLS_TIMEOUT = 500 * time.Millisecond

	// AUTH_TIMEOUT is the authorization wait time.
	AUTH_TIMEOUT = 2 * TLS_TIMEOUT

	// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
	DEFAULT_PING_INTERVAL = 2 * time.Minute

	// DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect.
	DEFAULT_PING_MAX_OUT = 2

	// CR_LF string
	CR_LF = "\r\n"

	// LEN_CR_LF hold onto the computed size.
	LEN_CR_LF = len(CR_LF)

	// DEFAULT_FLUSH_DEADLINE is the write/flush deadlines.
	DEFAULT_FLUSH_DEADLINE = 2 * time.Second

	// DEFAULT_HTTP_PORT is the default monitoring port.
	DEFAULT_HTTP_PORT = 8222

	// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
	ACCEPT_MIN_SLEEP = 10 * time.Millisecond

	// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
	ACCEPT_MAX_SLEEP = 1 * time.Second

	// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
	DEFAULT_ROUTE_CONNECT = 1 * time.Second

	// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
	DEFAULT_ROUTE_RECONNECT = 1 * time.Second

	// DEFAULT_ROUTE_DIAL Route dial timeout.
	DEFAULT_ROUTE_DIAL = 1 * time.Second

	// DEFAULT_LEAF_NODE_RECONNECT LeafNode reconnect interval.
	DEFAULT_LEAF_NODE_RECONNECT = time.Second

	// DEFAULT_LEAF_TLS_TIMEOUT TLS timeout for LeafNodes
	DEFAULT_LEAF_TLS_TIMEOUT = 2 * time.Second

	// PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors.
	PROTO_SNIPPET_SIZE = 32

	// MAX_MSG_ARGS Maximum possible number of arguments from MSG proto.
	MAX_MSG_ARGS = 4

	// MAX_PUB_ARGS Maximum possible number of arguments from PUB proto.
	MAX_PUB_ARGS = 3

	// DEFAULT_MAX_CLOSED_CLIENTS is the maximum number of closed connections we hold onto.
	DEFAULT_MAX_CLOSED_CLIENTS = 10000

	// DEFAULT_MAX_ACCOUNT_AE_RESPONSE_MAPS is for auto-expire response maps for imports.
	DEFAULT_MAX_ACCOUNT_AE_RESPONSE_MAPS = 100000

	// DEFAULT_MAX_ACCOUNT_INTERNAL_RESPONSE_MAPS is for non auto-expire response maps for imports.
	// These are present for non-singleton response types.
	DEFAULT_MAX_ACCOUNT_INTERNAL_RESPONSE_MAPS = 100000

	// DEFAULT_TTL_AE_RESPONSE_MAP is the default time to expire auto-response map entries.
	DEFAULT_TTL_AE_RESPONSE_MAP = 10 * time.Minute

	// DEFAULT_LAME_DUCK_DURATION is the time in which the server spreads
	// the closing of clients when signaled to go in lame duck mode.
	DEFAULT_LAME_DUCK_DURATION = 2 * time.Minute

	// DEFAULT_LEAFNODE_INFO_WAIT Route dial timeout.
	DEFAULT_LEAFNODE_INFO_WAIT = 1 * time.Second

	// DEFAULT_LEAFNODE_PORT is the default port for remote leafnode connections.
	DEFAULT_LEAFNODE_PORT = 7422

	// DEFAULT_CONNECT_ERROR_REPORTS is the number of attempts at which a
	// repeated failed route, gateway or leaf node connection is reported.
	// This is used for initial connection, that is, when the server has
	// never had a connection to the given endpoint. Once connected, and
	// if a disconnect occurs, DEFAULT_RECONNECT_ERROR_REPORTS is used
	// instead.
	// The default is to report every 3600 attempts (roughly every hour).
	DEFAULT_CONNECT_ERROR_REPORTS = 3600

	// DEFAULT_RECONNECT_ERROR_REPORTS is the default number of failed
	// attempt to reconnect a route, gateway or leaf node connection.
	// The default is to report every attempt.
	DEFAULT_RECONNECT_ERROR_REPORTS = 1

	// DEFAULT_RTT_MEASUREMENT_INTERVAL is how often we want to measure RTT from
	// this server to clients, routes, gateways or leafnode connections.
	DEFAULT_RTT_MEASUREMENT_INTERVAL = time.Hour

	// DEFAULT_ALLOW_RESPONSE_MAX_MSGS is the default number of responses allowed
	// for a reply subject.
	DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1

	// DEFAULT_ALLOW_RESPONSE_EXPIRATION is the default time allowed for a given
	// dynamic response permission.
	DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2 * time.Minute

	// DEFAULT_SERVICE_LATENCY_SAMPLING is the default sampling rate for service
	// latency metrics
	DEFAULT_SERVICE_LATENCY_SAMPLING = 100
)
<|endoftext|>


<commit_before>package statsd

import (
	"context"
	"net"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/atlassian/gostatsd"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/viper"
	"golang.org/x/time/rate"
)

// Server encapsulates all of the parameters necessary for starting up
// the statsd server. These can either be set via command line or directly.
type Server struct {
	Backends            []gostatsd.Backend
	ConsoleAddr         string
	CloudProvider       gostatsd.CloudProvider
	Limiter             *rate.Limiter
	DefaultTags         gostatsd.Tags
	ExpiryInterval      time.Duration
	FlushInterval       time.Duration
	MaxReaders          int
	MaxWorkers          int
	MaxQueueSize        int
	MaxConcurrentEvents int
	MaxEventQueueSize   int
	MetricsAddr         string
	Namespace           string
	PercentThreshold    []float64
	WebConsoleAddr      string
	CacheOptions
	Viper *viper.Viper
}

// Run runs the server until context signals done.
func (s *Server) Run(ctx context.Context) error {
	return s.RunWithCustomSocket(ctx, func() (net.PacketConn, error) {
		return net.ListenPacket("udp", s.MetricsAddr)
	})
}

// SocketFactory is an indirection layer over net.ListenPacket() to allow for different implementations.
type SocketFactory func() (net.PacketConn, error)

// RunWithCustomSocket runs the server until context signals done.
// Listening socket is created using sf.
func (s *Server) RunWithCustomSocket(ctx context.Context, sf SocketFactory) error {
	// 0. Start runnable backends
	var wgBackends sync.WaitGroup
	defer wgBackends.Wait()                                         // Wait for backends to shutdown
	ctxBack, cancelBack := context.WithCancel(context.Background()) // Separate context!
	defer cancelBack()                                              // Tell backends to shutdown
	for _, b := range s.Backends {
		if b, ok := b.(gostatsd.RunnableBackend); ok {
			wgBackends.Add(1)
			go func(b gostatsd.RunnableBackend) {
				defer wgBackends.Done()
				if err := b.Run(ctxBack); unexpectedErr(err) {
					log.Panicf("Backend %s quit unexpectedly: %v", b.Name(), err)
				}
			}(b)
		}
	}

	// 1. Start the Dispatcher
	factory := agrFactory{
		percentThresholds: s.PercentThreshold,
		expiryInterval:    s.ExpiryInterval,
	}
	dispatcher := NewMetricDispatcher(s.MaxWorkers, s.MaxQueueSize, &factory)

	var wgDispatcher sync.WaitGroup
	defer wgDispatcher.Wait()                                       // Wait for dispatcher to shutdown
	ctxDisp, cancelDisp := context.WithCancel(context.Background()) // Separate context!
	defer cancelDisp()                                              // Tell the dispatcher to shutdown
	wgDispatcher.Add(1)
	go func() {
		defer wgDispatcher.Done()
		if dispErr := dispatcher.Run(ctxDisp); unexpectedErr(dispErr) {
			log.Panicf("Dispatcher quit unexpectedly: %v", dispErr)
		}
	}()

	// 2. Start handlers
	ip := gostatsd.UnknownIP

	var handler Handler
	handler = NewDispatchingHandler(dispatcher, s.Backends, s.DefaultTags, uint(s.MaxConcurrentEvents))
	if s.CloudProvider != nil {
		ch := NewCloudHandler(s.CloudProvider, handler, s.Limiter, &s.CacheOptions)
		handler = ch
		var wgCloudHandler sync.WaitGroup
		defer wgCloudHandler.Wait()                                           // Wait for handler to shutdown
		ctxHandler, cancelHandler := context.WithCancel(context.Background()) // Separate context!
		defer cancelHandler()                                                 // Tell the handler to shutdown
		wgCloudHandler.Add(1)
		go func() {
			defer wgCloudHandler.Done()
			if handlerErr := ch.Run(ctxHandler); unexpectedErr(handlerErr) {
				log.Panicf("Cloud handler quit unexpectedly: %v", handlerErr)
			}
		}()
		selfIP, err := s.CloudProvider.SelfIP()
		if err != nil {
			log.Warnf("Failed to get self ip: %v", err)
		} else {
			ip = selfIP
		}
	}

	// 3. Start the Receiver
	var wgReceiver sync.WaitGroup
	defer wgReceiver.Wait() // Wait for all receivers to finish

	// Open socket
	c, err := sf()
	if err != nil {
		return err
	}
	defer func() {
		// This makes receivers error out and stop
		if e := c.Close(); e != nil {
			log.Warnf("Error closing socket: %v", e)
		}
	}()

	receiver := NewMetricReceiver(s.Namespace, handler)
	wgReceiver.Add(s.MaxReaders)
	for r := 0; r < s.MaxReaders; r++ {
		go func() {
			defer wgReceiver.Done()
			if e := receiver.Receive(ctx, c); unexpectedErr(e) {
				log.Panicf("Receiver quit unexpectedly: %v", e)
			}
		}()
	}

	// 4. Start the Flusher
	hostname := getHost()
	flusher := NewMetricFlusher(s.FlushInterval, dispatcher, receiver, handler, s.Backends, ip, hostname)
	var wgFlusher sync.WaitGroup
	defer wgFlusher.Wait() // Wait for the Flusher to finish
	wgFlusher.Add(1)
	go func() {
		defer wgFlusher.Done()
		if err := flusher.Run(ctx); unexpectedErr(err) {
			log.Panicf("Flusher quit unexpectedly: %v", err)
		}
	}()

	// 5. Start the console(s)
	if s.ConsoleAddr != "" {
		console := ConsoleServer{s.ConsoleAddr, receiver, dispatcher, flusher}
		go console.ListenAndServe(ctx)
	}
	//if s.WebConsoleAddr != "" {
	//	console := WebConsoleServer{s.WebConsoleAddr, aggregator}
	//	go console.ListenAndServe()
	//}

	// 6. Send events on start and on stop
	defer sendStopEvent(handler, ip, hostname)
	sendStartEvent(ctx, handler, ip, hostname)

	// 7. Listen until done
	<-ctx.Done()
	return ctx.Err()
}

func sendStartEvent(ctx context.Context, handler Handler, selfIP gostatsd.IP, hostname string) {
	err := handler.DispatchEvent(ctx, &gostatsd.Event{
		Title:        "Gostatsd started",
		Text:         "Gostatsd started",
		DateHappened: time.Now().Unix(),
		Hostname:     hostname,
		SourceIP:     selfIP,
		Priority:     gostatsd.PriLow,
	})
	if unexpectedErr(err) {
		log.Warnf("Failed to send start event: %v", err)
	}
}

func sendStopEvent(handler Handler, selfIP gostatsd.IP, hostname string) {
	ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancelFunc()
	err := handler.DispatchEvent(ctx, &gostatsd.Event{
		Title:        "Gostatsd stopped",
		Text:         "Gostatsd stopped",
		DateHappened: time.Now().Unix(),
		Hostname:     hostname,
		SourceIP:     selfIP,
		Priority:     gostatsd.PriLow,
	})
	if unexpectedErr(err) {
		log.Warnf("Failed to send stop event: %v", err)
	}
	handler.WaitForEvents()
}

func getHost() string {
	host, err := os.Hostname()
	if err != nil {
		log.Warnf("Cannot get hostname: %v", err)
		return ""
	}
	return host
}

type agrFactory struct {
	percentThresholds []float64
	expiryInterval    time.Duration
}

func (af *agrFactory) Create() Aggregator {
	return NewMetricAggregator(af.percentThresholds, af.expiryInterval)
}

func toStringSlice(fs []float64) []string {
	s := make([]string, len(fs))
	for i, f := range fs {
		s[i] = strconv.FormatFloat(f, 'f', -1, 64)
	}
	return s
}

func unexpectedErr(err error) bool {
	return err != nil && err != context.Canceled && err != context.DeadlineExceeded
}
<commit_msg>Disable linter for the line<commit_after>package statsd

import (
	"context"
	"net"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/atlassian/gostatsd"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/viper"
	"golang.org/x/time/rate"
)

// Server encapsulates all of the parameters necessary for starting up
// the statsd server. These can either be set via command line or directly.
type Server struct {
	Backends            []gostatsd.Backend
	ConsoleAddr         string
	CloudProvider       gostatsd.CloudProvider
	Limiter             *rate.Limiter
	DefaultTags         gostatsd.Tags
	ExpiryInterval      time.Duration
	FlushInterval       time.Duration
	MaxReaders          int
	MaxWorkers          int
	MaxQueueSize        int
	MaxConcurrentEvents int
	MaxEventQueueSize   int
	MetricsAddr         string
	Namespace           string
	PercentThreshold    []float64
	WebConsoleAddr      string
	CacheOptions
	Viper *viper.Viper
}

// Run runs the server until context signals done.
func (s *Server) Run(ctx context.Context) error {
	return s.RunWithCustomSocket(ctx, func() (net.PacketConn, error) {
		return net.ListenPacket("udp", s.MetricsAddr)
	})
}

// SocketFactory is an indirection layer over net.ListenPacket() to allow for different implementations.
type SocketFactory func() (net.PacketConn, error)

// RunWithCustomSocket runs the server until context signals done.
// Listening socket is created using sf.
func (s *Server) RunWithCustomSocket(ctx context.Context, sf SocketFactory) error {
	// 0. Start runnable backends
	var wgBackends sync.WaitGroup
	defer wgBackends.Wait()                                         // Wait for backends to shutdown
	ctxBack, cancelBack := context.WithCancel(context.Background()) // Separate context!
	defer cancelBack()                                              // Tell backends to shutdown
	for _, b := range s.Backends {
		if b, ok := b.(gostatsd.RunnableBackend); ok {
			wgBackends.Add(1)
			go func(b gostatsd.RunnableBackend) {
				defer wgBackends.Done()
				if err := b.Run(ctxBack); unexpectedErr(err) {
					log.Panicf("Backend %s quit unexpectedly: %v", b.Name(), err)
				}
			}(b)
		}
	}

	// 1. Start the Dispatcher
	factory := agrFactory{
		percentThresholds: s.PercentThreshold,
		expiryInterval:    s.ExpiryInterval,
	}
	dispatcher := NewMetricDispatcher(s.MaxWorkers, s.MaxQueueSize, &factory)

	var wgDispatcher sync.WaitGroup
	defer wgDispatcher.Wait()                                       // Wait for dispatcher to shutdown
	ctxDisp, cancelDisp := context.WithCancel(context.Background()) // Separate context!
	defer cancelDisp()                                              // Tell the dispatcher to shutdown
	wgDispatcher.Add(1)
	go func() {
		defer wgDispatcher.Done()
		if dispErr := dispatcher.Run(ctxDisp); unexpectedErr(dispErr) {
			log.Panicf("Dispatcher quit unexpectedly: %v", dispErr)
		}
	}()

	// 2. Start handlers
	ip := gostatsd.UnknownIP

	var handler Handler // nolint: gosimple
	handler = NewDispatchingHandler(dispatcher, s.Backends, s.DefaultTags, uint(s.MaxConcurrentEvents))
	if s.CloudProvider != nil {
		ch := NewCloudHandler(s.CloudProvider, handler, s.Limiter, &s.CacheOptions)
		handler = ch
		var wgCloudHandler sync.WaitGroup
		defer wgCloudHandler.Wait()                                           // Wait for handler to shutdown
		ctxHandler, cancelHandler := context.WithCancel(context.Background()) // Separate context!
		defer cancelHandler()                                                 // Tell the handler to shutdown
		wgCloudHandler.Add(1)
		go func() {
			defer wgCloudHandler.Done()
			if handlerErr := ch.Run(ctxHandler); unexpectedErr(handlerErr) {
				log.Panicf("Cloud handler quit unexpectedly: %v", handlerErr)
			}
		}()
		selfIP, err := s.CloudProvider.SelfIP()
		if err != nil {
			log.Warnf("Failed to get self ip: %v", err)
		} else {
			ip = selfIP
		}
	}

	// 3. Start the Receiver
	var wgReceiver sync.WaitGroup
	defer wgReceiver.Wait() // Wait for all receivers to finish

	// Open socket
	c, err := sf()
	if err != nil {
		return err
	}
	defer func() {
		// This makes receivers error out and stop
		if e := c.Close(); e != nil {
			log.Warnf("Error closing socket: %v", e)
		}
	}()

	receiver := NewMetricReceiver(s.Namespace, handler)
	wgReceiver.Add(s.MaxReaders)
	for r := 0; r < s.MaxReaders; r++ {
		go func() {
			defer wgReceiver.Done()
			if e := receiver.Receive(ctx, c); unexpectedErr(e) {
				log.Panicf("Receiver quit unexpectedly: %v", e)
			}
		}()
	}

	// 4. Start the Flusher
	hostname := getHost()
	flusher := NewMetricFlusher(s.FlushInterval, dispatcher, receiver, handler, s.Backends, ip, hostname)
	var wgFlusher sync.WaitGroup
	defer wgFlusher.Wait() // Wait for the Flusher to finish
	wgFlusher.Add(1)
	go func() {
		defer wgFlusher.Done()
		if err := flusher.Run(ctx); unexpectedErr(err) {
			log.Panicf("Flusher quit unexpectedly: %v", err)
		}
	}()

	// 5. Start the console(s)
	if s.ConsoleAddr != "" {
		console := ConsoleServer{s.ConsoleAddr, receiver, dispatcher, flusher}
		go console.ListenAndServe(ctx)
	}
	//if s.WebConsoleAddr != "" {
	//	console := WebConsoleServer{s.WebConsoleAddr, aggregator}
	//	go console.ListenAndServe()
	//}

	// 6. Send events on start and on stop
	defer sendStopEvent(handler, ip, hostname)
	sendStartEvent(ctx, handler, ip, hostname)

	// 7. Listen until done
	<-ctx.Done()
	return ctx.Err()
}

func sendStartEvent(ctx context.Context, handler Handler, selfIP gostatsd.IP, hostname string) {
	err := handler.DispatchEvent(ctx, &gostatsd.Event{
		Title:        "Gostatsd started",
		Text:         "Gostatsd started",
		DateHappened: time.Now().Unix(),
		Hostname:     hostname,
		SourceIP:     selfIP,
		Priority:     gostatsd.PriLow,
	})
	if unexpectedErr(err) {
		log.Warnf("Failed to send start event: %v", err)
	}
}

func sendStopEvent(handler Handler, selfIP gostatsd.IP, hostname string) {
	ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancelFunc()
	err := handler.DispatchEvent(ctx, &gostatsd.Event{
		Title:        "Gostatsd stopped",
		Text:         "Gostatsd stopped",
		DateHappened: time.Now().Unix(),
		Hostname:     hostname,
		SourceIP:     selfIP,
		Priority:     gostatsd.PriLow,
	})
	if unexpectedErr(err) {
		log.Warnf("Failed to send stop event: %v", err)
	}
	handler.WaitForEvents()
}

func getHost() string {
	host, err := os.Hostname()
	if err != nil {
		log.Warnf("Cannot get hostname: %v", err)
		return ""
	}
	return host
}

type agrFactory struct {
	percentThresholds []float64
	expiryInterval    time.Duration
}

func (af *agrFactory) Create() Aggregator {
	return NewMetricAggregator(af.percentThresholds, af.expiryInterval)
}

func toStringSlice(fs []float64) []string {
	s := make([]string, len(fs))
	for i, f := range fs {
		s[i] = strconv.FormatFloat(f, 'f', -1, 64)
	}
	return s
}

func unexpectedErr(err error) bool {
	return err != nil && err != context.Canceled && err != context.DeadlineExceeded
}
<|endoftext|>


<commit_before>// This file is based on the code from https://github.com/spf13/cobra
// Original copyright follows
//Copyright 2015 Red Hat Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tools

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

func printOptions(out io.Writer, cmd *cobra.Command, name string) error {
	if cmd.HasParent() {
		return printFlagUsages(out, cmd.NonInheritedFlags(), "\n**Options**\n\n", false)
	} else {
		return printFlagUsages(out, cmd.NonInheritedFlags(), "\n## Global options\n\n", true)
	}

	return nil
}

func printSubcommands(out io.Writer, cmd *cobra.Command) error {
	children := cmd.Commands()
	sort.Sort(byName(children))

	var filtered []*cobra.Command

	for _, c := range children {
		if c.IsAvailableCommand() && !c.IsAdditionalHelpTopicCommand() {
			filtered = append(filtered, c)
		}
	}
	if len(filtered) == 0 {
		return nil
	}

	if _, err := fmt.Fprintf(out, "\n**Subcommands**\n\n"); err != nil {
		return err
	}

	name := cmd.CommandPath()
	for _, c := range filtered {
		cname := name + " " + c.Name()
		// FIXME: use better link name generation scheme
		link := "#" + strings.Replace(cname, " ", "-", -1)
		fmt.Fprintf(out, "* [%s](%s) - %s\n", cname, link, c.Short)

	}

	for _, c := range filtered {
		if err := GenMarkdown(c, out); err != nil {
			return err
		}
	}

	return nil
}

// GenMarkdownCustom creates custom markdown output.
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
	cmd.InitDefaultHelpCmd()
	cmd.InitDefaultHelpFlag()

	name := cmd.CommandPath()

	short := cmd.Short
	long := cmd.Long
	if len(long) == 0 {
		long = short
	}

	_, err := fmt.Fprintf(w, "## %s\n\n", name)
	if err == nil {
		_, err = fmt.Fprintf(w, "%s\n\n", short)
	}
	if err == nil {
		_, err = fmt.Fprintf(w, "**Synopsis**\n\n%s\n\n", long)
	}

	if err == nil && cmd.Runnable() {
		_, err = fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine())
	}

	if err == nil && len(cmd.Example) > 0 {
		_, err = fmt.Fprintf(w, "**Examples**\n\n```\n%s\n```\n\n", cmd.Example)
	}

	// we want global options to be at the end of the document
	if err == nil && cmd.HasParent() {
		err = printOptions(w, cmd, name)
	}

	if err == nil {
		err = printSubcommands(w, cmd)
	}

	if err == nil && !cmd.HasParent() {
		err = printOptions(w, cmd, name)
	}

	return err
}

// GenMarkdownTree will generate a markdown page for this command and all
// descendants in the directory given. The header may be nil.
// This function may not work correctly if your command names have `-` in them.
// If you have `cmd` with two subcmds, `sub` and `sub-third`,
// and `sub` has a subcommand called `third`, it is undefined which
// help output will be in the file `cmd-sub-third.1`.
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
	return GenMarkdownTreeCustom(cmd, dir)
}

// GenMarkdownTreeCustom is the the same as GenMarkdownTree, but
// with custom filePrepender and linkHandler.
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string) error {
	basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
	filename := filepath.Join(dir, basename)
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := GenMarkdown(cmd, f); err != nil {
		return err
	}
	return nil
}

type byName []*cobra.Command

func (s byName) Len() int           { return len(s) }
func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }

func printFlagUsages(out io.Writer, f *pflag.FlagSet, title string, includeHelpFlag bool) error {
	titlePrinted := false
	var err error
	f.VisitAll(func(flag *pflag.Flag) {
		if err != nil {
			return
		}

		if flag.Deprecated != "" || flag.Hidden {
			return
		}

		if !includeHelpFlag && flag.Name == "help" {
			return
		}

		if !titlePrinted {
			if _, err = out.Write([]byte(title)); err != nil {
				return
			}
			titlePrinted = true
		}

		varStr := ""
		varname, usage := pflag.UnquoteUsage(flag)
		if varname != "" {
			varStr = " " + varname
		}
		if flag.NoOptDefVal != "" {
			switch flag.Value.Type() {
			case "string":
				varStr += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
			case "bool":
				if flag.NoOptDefVal != "true" {
					varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
				}
			case "count":
				if flag.NoOptDefVal != "+1" {
					varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
				}
			default:
				varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
			}
		}

		if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
			_, err = fmt.Fprintf(out, "\n```\n-%s, --%s%s\n```\n", flag.Shorthand, flag.Name, varStr)
		} else {
			_, err = fmt.Fprintf(out, "\n```\n--%s%s\n```\n", flag.Name, varStr)
		}

		if err == nil {
			_, err = fmt.Fprintf(out, "%s\n", usage)
		}

		if err == nil && !defaultIsZeroValue(flag) {
			if flag.Value.Type() == "string" {
				_, err = fmt.Fprintf(out, " **(default value:** `%q`)\n", flag.DefValue)
			} else {
				_, err = fmt.Fprintf(out, " **(default value:** `%s`)\n", flag.DefValue)
			}
		}
	})

	return err
}

// defaultIsZeroValue returns true if the default value for this flag represents
// a zero value.
func defaultIsZeroValue(f *pflag.Flag) bool {
	switch f.Value.Type() {
	case "bool":
		return f.DefValue == "false"
	case "duration":
		// Beginning in Go 1.7, duration zero values are "0s"
		return f.DefValue == "0" || f.DefValue == "0s"
	case "int", "int8", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "count", "float32", "float64":
		return f.DefValue == "0"
	case "string":
		return f.DefValue == ""
	case "ip", "ipMask", "ipNet":
		return f.DefValue == "<nil>"
	case "intSlice", "stringSlice", "stringArray":
		return f.DefValue == "[]"
	default:
		switch f.Value.String() {
		case "false":
			return true
		case "<nil>":
			return true
		case "":
			return true
		case "0":
			return true
		}
		return false
	}
}
<commit_msg>Fix codeclimate issues<commit_after>// This file is based on the code from https://github.com/spf13/cobra
// Original copyright follows
//Copyright 2015 Red Hat Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tools

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

func printOptions(out io.Writer, cmd *cobra.Command, name string) error {
	if cmd.HasParent() {
		return printFlagUsages(out, cmd.NonInheritedFlags(), "\n**Options**\n\n", false)
	}

	return printFlagUsages(out, cmd.NonInheritedFlags(), "\n## Global options\n\n", true)
}

func printSubcommands(out io.Writer, cmd *cobra.Command) error {
	children := cmd.Commands()
	sort.Sort(byName(children))

	var filtered []*cobra.Command

	for _, c := range children {
		if c.IsAvailableCommand() && !c.IsAdditionalHelpTopicCommand() {
			filtered = append(filtered, c)
		}
	}
	if len(filtered) == 0 {
		return nil
	}

	if _, err := fmt.Fprintf(out, "\n**Subcommands**\n\n"); err != nil {
		return err
	}

	name := cmd.CommandPath()
	for _, c := range filtered {
		cname := name + " " + c.Name()
		// FIXME: use better link name generation scheme
		link := "#" + strings.Replace(cname, " ", "-", -1)
		fmt.Fprintf(out, "* [%s](%s) - %s\n", cname, link, c.Short)

	}

	for _, c := range filtered {
		if err := GenMarkdown(c, out); err != nil {
			return err
		}
	}

	return nil
}

// GenMarkdown creates markdown output.
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
	cmd.InitDefaultHelpCmd()
	cmd.InitDefaultHelpFlag()

	name := cmd.CommandPath()

	short := cmd.Short
	long := cmd.Long
	if len(long) == 0 {
		long = short
	}

	_, err := fmt.Fprintf(w, "## %s\n\n", name)
	if err == nil {
		_, err = fmt.Fprintf(w, "%s\n\n", short)
	}
	if err == nil {
		_, err = fmt.Fprintf(w, "**Synopsis**\n\n%s\n\n", long)
	}

	if err == nil && cmd.Runnable() {
		_, err = fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine())
	}

	if err == nil && len(cmd.Example) > 0 {
		_, err = fmt.Fprintf(w, "**Examples**\n\n```\n%s\n```\n\n", cmd.Example)
	}

	// we want global options to be at the end of the document
	if err == nil && cmd.HasParent() {
		err = printOptions(w, cmd, name)
	}

	if err == nil {
		err = printSubcommands(w, cmd)
	}

	if err == nil && !cmd.HasParent() {
		err = printOptions(w, cmd, name)
	}

	return err
}

// GenMarkdownTree will generate a markdown page for this command and all
// descendants in the directory given. The header may be nil.
// This function may not work correctly if your command names have `-` in them.
// If you have `cmd` with two subcmds, `sub` and `sub-third`,
// and `sub` has a subcommand called `third`, it is undefined which
// help output will be in the file `cmd-sub-third.1`.
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
	return GenMarkdownTreeCustom(cmd, dir)
}

// GenMarkdownTreeCustom is the the same as GenMarkdownTree, but
// with custom filePrepender and linkHandler.
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string) error {
	basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
	filename := filepath.Join(dir, basename)
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	return GenMarkdown(cmd, f)
}

type byName []*cobra.Command

func (s byName) Len() int           { return len(s) }
func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }

func printFlagUsages(out io.Writer, f *pflag.FlagSet, title string, includeHelpFlag bool) error {
	titlePrinted := false
	var err error
	f.VisitAll(func(flag *pflag.Flag) {
		if err != nil {
			return
		}

		if flag.Deprecated != "" || flag.Hidden {
			return
		}

		if !includeHelpFlag && flag.Name == "help" {
			return
		}

		if !titlePrinted {
			if _, err = out.Write([]byte(title)); err != nil {
				return
			}
			titlePrinted = true
		}

		varStr := ""
		varname, usage := pflag.UnquoteUsage(flag)
		if varname != "" {
			varStr = " " + varname
		}
		if flag.NoOptDefVal != "" {
			switch flag.Value.Type() {
			case "string":
				varStr += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
			case "bool":
				if flag.NoOptDefVal != "true" {
					varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
				}
			case "count":
				if flag.NoOptDefVal != "+1" {
					varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
				}
			default:
				varStr += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
			}
		}

		if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
			_, err = fmt.Fprintf(out, "\n```\n-%s, --%s%s\n```\n", flag.Shorthand, flag.Name, varStr)
		} else {
			_, err = fmt.Fprintf(out, "\n```\n--%s%s\n```\n", flag.Name, varStr)
		}

		if err == nil {
			_, err = fmt.Fprintf(out, "%s\n", usage)
		}

		if err == nil && !defaultIsZeroValue(flag) {
			if flag.Value.Type() == "string" {
				_, err = fmt.Fprintf(out, " **(default value:** `%q`)\n", flag.DefValue)
			} else {
				_, err = fmt.Fprintf(out, " **(default value:** `%s`)\n", flag.DefValue)
			}
		}
	})

	return err
}

// defaultIsZeroValue returns true if the default value for this flag represents
// a zero value.
func defaultIsZeroValue(f *pflag.Flag) bool {
	switch f.Value.Type() {
	case "bool":
		return f.DefValue == "false"
	case "duration":
		// Beginning in Go 1.7, duration zero values are "0s"
		return f.DefValue == "0" || f.DefValue == "0s"
	case "int", "int8", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "count", "float32", "float64":
		return f.DefValue == "0"
	case "string":
		return f.DefValue == ""
	case "ip", "ipMask", "ipNet":
		return f.DefValue == "<nil>"
	case "intSlice", "stringSlice", "stringArray":
		return f.DefValue == "[]"
	default:
		switch f.Value.String() {
		case "false":
			return true
		case "<nil>":
			return true
		case "":
			return true
		case "0":
			return true
		}
		return false
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/SpaceLeap/go-mavlink/mavlink"
	"github.com/SpaceLeap/go-mavlink/mavlink/common"
	"github.com/ungerik/go-dry"
	"github.com/ungerik/goserial"
)

var (
	port       string
	baud       int
	maxPackets int
	quitAfter  time.Duration
	timeout    time.Duration

	stop bool
)

func main() {
	common.Init()

	flag.StringVar(&port, "port", "", "Serial port to connect to")
	flag.IntVar(&baud, "baud", 115200, "Speed of the connection")
	flag.IntVar(&maxPackets, "max", 10, "Quit program after this number of packets")
	flag.DurationVar(&quitAfter, "quitafter", time.Second*3, "Quit program after this duration")
	flag.DurationVar(&timeout, "timeout", time.Second, "Read timeout per packet")
	flag.Parse()

	if port == "" {
		if flag.NArg() > 0 {
			port = flag.Arg(0)
		} else {
			ports := serial.ListPorts()
			if len(ports) == 1 {
				port = ports[0]
			} else {
				fmt.Fprintln(os.Stderr, "Call with -port=PORT")
				flag.PrintDefaults()
				fmt.Fprintln(os.Stderr, "\nAvailable as PORT are:")
				for _, p := range ports {
					fmt.Fprintln(os.Stderr, "  ", p)
				}
				return
			}
		}
	}

	serialConn, err := serial.OpenDefault(port, serial.Baud(baud), timeout)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Opened serial port", port)
	defer func() {
		log.Println("Closed serial port", port, "with error", serialConn.Close())
	}()

	//conn := mavlink.NewConnection(serialConn, 99)

	err = mavlink.Send(serialConn, 0, 0, 0, common.NewHeartbeat())
	if err != nil {
		log.Println(err)
	}

	err = mavlink.Send(serialConn, 0, 0, 0, &common.Ping{})
	if err != nil {
		log.Println(err)
	}

	err = mavlink.Send(serialConn, 0, 0, 0, &common.ParamRequestList{})
	if err != nil {
		log.Println(err)
	}

	go func() {
		dry.WaitForStdin("Press any key to quit")
		stop = true
	}()

	time.AfterFunc(quitAfter, func() { stop = true })

	for i := 0; i < maxPackets && !stop; i++ {
		packet, err := mavlink.Receive(serialConn)
		if err == nil {
			log.Println(packet)
		} else {
			log.Println(err)
		}
	}
}
<commit_msg>use tarm/goserial<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/SpaceLeap/go-mavlink/mavlink"
	"github.com/SpaceLeap/go-mavlink/mavlink/common"
	tarm "github.com/tarm/goserial"
	"github.com/ungerik/go-dry"
	"github.com/ungerik/goserial"
)

var (
	port       string
	baud       int
	maxPackets int
	quitAfter  time.Duration
	timeout    time.Duration

	stop bool
)

func main() {
	common.Init()

	flag.StringVar(&port, "port", "", "Serial port to connect to")
	flag.IntVar(&baud, "baud", 115200, "Speed of the connection")
	flag.IntVar(&maxPackets, "max", 10, "Quit program after this number of packets")
	flag.DurationVar(&quitAfter, "quitafter", time.Second*3, "Quit program after this duration")
	flag.DurationVar(&timeout, "timeout", time.Second, "Read timeout per packet")
	flag.Parse()

	if port == "" {
		if flag.NArg() > 0 {
			port = flag.Arg(0)
		} else {
			ports := serial.ListPorts()
			if len(ports) == 1 {
				port = ports[0]
			} else {
				fmt.Fprintln(os.Stderr, "Call with -port=PORT")
				flag.PrintDefaults()
				fmt.Fprintln(os.Stderr, "\nAvailable as PORT are:")
				for _, p := range ports {
					fmt.Fprintln(os.Stderr, "  ", p)
				}
				return
			}
		}
	}

	// serialConn, err := serial.OpenDefault(port, serial.Baud(baud), timeout)
	serialConn, err := tarm.OpenPort(&tarm.Config{Name: port, Baud: baud})
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Opened serial port", port)
	defer func() {
		log.Println("Closed serial port", port, "with error", serialConn.Close())
	}()

	//conn := mavlink.NewConnection(serialConn, 99)

	err = mavlink.Send(serialConn, 0, 0, 0, common.NewHeartbeat())
	if err != nil {
		log.Println(err)
	}

	err = mavlink.Send(serialConn, 0, 0, 0, &common.Ping{})
	if err != nil {
		log.Println(err)
	}

	err = mavlink.Send(serialConn, 0, 0, 0, &common.ParamRequestList{})
	if err != nil {
		log.Println(err)
	}

	go func() {
		dry.WaitForStdin("Press any key to quit")
		stop = true
	}()

	time.AfterFunc(quitAfter, func() { stop = true })

	for i := 0; i < maxPackets && !stop; i++ {
		packet, err := mavlink.Receive(serialConn)
		if err == nil {
			log.Println(packet)
		} else {
			log.Println(err)
		}
	}
}
<|endoftext|>


<commit_before>package termite

import (
	"fmt"
	"path/filepath"
	"os"
	"log"
	"io/ioutil"
	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/unionfs"
	"rpc"
	"sync"
)

// State associated with one master.
type Mirror struct {
	daemon        *WorkerDaemon
	fileServer    *rpc.Client
	rpcFs         *RpcFs
	writableRoot  string

	fuseFileSystemsMutex sync.Mutex
	fuseFileSystems      []*WorkerFuseFs
	workingFileSystems   map[*WorkerFuseFs]string
}

func (me *Mirror) ReturnFuse(wfs *WorkerFuseFs) {
	// TODO - could be more fine-grained here.
	wfs.unionFs.DropBranchCache()
	wfs.unionFs.DropDeletionCache()

	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	me.fuseFileSystems = append(me.fuseFileSystems, wfs)
	me.workingFileSystems[wfs] = "", false
}

func (me *Mirror) DiscardFuse(wfs *WorkerFuseFs) {
	wfs.Stop()

	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	me.workingFileSystems[wfs] = "", false
}

func (me *Mirror) getWorkerFuseFs(name string) (f *WorkerFuseFs, err os.Error) {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	l := len(me.fuseFileSystems)
	if l > 0 {
		f = me.fuseFileSystems[l-1]
		me.fuseFileSystems = me.fuseFileSystems[:l-1]
	}
	if f == nil {
		f, err = me.newWorkerFuseFs()
	}
	me.workingFileSystems[f] = name
	return f, err
}

func (me *Mirror) Update(req *UpdateRequest, rep *UpdateResponse) os.Error {
	return me.rpcFs.Update(req, rep)
}

func (me *Mirror) Run(req *WorkRequest, rep *WorkReply) os.Error {
	task, err := me.newWorkerTask(req, rep)

	err = task.Run()
	if err != nil {
		log.Println("Error", err)
		return err
	}

	updateReq := UpdateRequest{
	Files: rep.Files,
	}
	updateRep := UpdateResponse{}
	err = me.rpcFs.Update(&updateReq, &updateRep)
	if err != nil {
		// TODO - fatal?
		log.Println("Update failed.")
	}

	summary := rep
	// Trim output.

	summary.Stdout = trim(summary.Stdout)
	summary.Stderr = trim(summary.Stderr)

	log.Println("sending back", summary)
	return nil
}

const _DELETIONS = "DELETIONS"

func (me *Mirror) newWorkerFuseFs() (*WorkerFuseFs, os.Error) {
	w := WorkerFuseFs{}

	tmpDir, err := ioutil.TempDir("", "rpcfs-tmp")
	w.tmpDir = tmpDir
	type dirInit struct {
		dst *string
		val string
	}

	for _, v := range []dirInit{
		dirInit{&w.rwDir, "rw"},
		dirInit{&w.mount, "mnt"},
	} {
		*v.dst = filepath.Join(w.tmpDir, v.val)
		err = os.Mkdir(*v.dst, 0700)
		if err != nil {
			return nil, err
		}
	}

	rwFs := fuse.NewLoopbackFileSystem(w.rwDir)

	ttl := 5.0
	opts := unionfs.UnionFsOptions{
		BranchCacheTTLSecs:   ttl,
		DeletionCacheTTLSecs: ttl,
		DeletionDirName:      _DELETIONS,
	}
	mOpts := fuse.FileSystemOptions{
		EntryTimeout:    ttl,
		AttrTimeout:     ttl,
		NegativeTimeout: 0.01,
	}

	w.unionFs = unionfs.NewUnionFs("ufs", []fuse.FileSystem{rwFs, me.rpcFs}, opts)
	swFs := []fuse.SwitchedFileSystem{
		{"dev", &DevNullFs{}, true},
		{"", me.rpcFs, false},

		// TODO - configurable.
		{"tmp", w.unionFs, false},
		{me.writableRoot, w.unionFs, false},
	}

	conn := fuse.NewFileSystemConnector(fuse.NewSwitchFileSystem(swFs), &mOpts)
	w.MountState = fuse.NewMountState(conn)
	w.MountState.Mount(w.mount, &fuse.MountOptions{AllowOther: true})
	if err != nil {
		return nil, err
	}

	go w.MountState.Loop(true)

	return &w, nil
}

func (me *Mirror) newWorkerTask(req *WorkRequest, rep *WorkReply) (*WorkerTask, os.Error) {
	fuseFs, err := me.getWorkerFuseFs(fmt.Sprintf("%v", req))
	if err != nil {
		return nil, err
	}
	stdin := me.daemon.pending.WaitConnection(req.StdinId)
	return &WorkerTask{
		WorkRequest:  req,
		WorkReply:    rep,
		stdinConn:    stdin,
		masterWorker: me,
		fuseFs:       fuseFs,
	}, nil
}

func (me *Mirror) Status(req *StatusRequest, rep *StatusReply) os.Error {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()

	rep.Processes += len(me.workingFileSystems)
	return nil
}

func (me *Mirror) FileContent(req *ContentRequest, rep *ContentResponse) os.Error {
	return me.daemon.contentServer.FileContent(req, rep)
}
<commit_msg>Tune down number of background tasks.<commit_after>package termite

import (
	"fmt"
	"path/filepath"
	"os"
	"log"
	"io/ioutil"
	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/unionfs"
	"rpc"
	"sync"
)

// State associated with one master.
type Mirror struct {
	daemon        *WorkerDaemon
	fileServer    *rpc.Client
	rpcFs         *RpcFs
	writableRoot  string

	fuseFileSystemsMutex sync.Mutex
	fuseFileSystems      []*WorkerFuseFs
	workingFileSystems   map[*WorkerFuseFs]string
}

func (me *Mirror) ReturnFuse(wfs *WorkerFuseFs) {
	// TODO - could be more fine-grained here.
	wfs.unionFs.DropBranchCache()
	wfs.unionFs.DropDeletionCache()

	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	me.fuseFileSystems = append(me.fuseFileSystems, wfs)
	me.workingFileSystems[wfs] = "", false
}

func (me *Mirror) DiscardFuse(wfs *WorkerFuseFs) {
	wfs.Stop()

	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	me.workingFileSystems[wfs] = "", false
}

func (me *Mirror) getWorkerFuseFs(name string) (f *WorkerFuseFs, err os.Error) {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()
	l := len(me.fuseFileSystems)
	if l > 0 {
		f = me.fuseFileSystems[l-1]
		me.fuseFileSystems = me.fuseFileSystems[:l-1]
	}
	if f == nil {
		f, err = me.newWorkerFuseFs()
	}
	me.workingFileSystems[f] = name
	return f, err
}

func (me *Mirror) Update(req *UpdateRequest, rep *UpdateResponse) os.Error {
	return me.rpcFs.Update(req, rep)
}

func (me *Mirror) Run(req *WorkRequest, rep *WorkReply) os.Error {
	task, err := me.newWorkerTask(req, rep)

	err = task.Run()
	if err != nil {
		log.Println("Error", err)
		return err
	}

	updateReq := UpdateRequest{
	Files: rep.Files,
	}
	updateRep := UpdateResponse{}
	err = me.rpcFs.Update(&updateReq, &updateRep)
	if err != nil {
		// TODO - fatal?
		log.Println("Update failed.")
	}

	summary := rep
	// Trim output.

	summary.Stdout = trim(summary.Stdout)
	summary.Stderr = trim(summary.Stderr)

	log.Println("sending back", summary)
	return nil
}

const _DELETIONS = "DELETIONS"

func (me *Mirror) newWorkerFuseFs() (*WorkerFuseFs, os.Error) {
	w := WorkerFuseFs{}

	tmpDir, err := ioutil.TempDir("", "rpcfs-tmp")
	w.tmpDir = tmpDir
	type dirInit struct {
		dst *string
		val string
	}

	for _, v := range []dirInit{
		dirInit{&w.rwDir, "rw"},
		dirInit{&w.mount, "mnt"},
	} {
		*v.dst = filepath.Join(w.tmpDir, v.val)
		err = os.Mkdir(*v.dst, 0700)
		if err != nil {
			return nil, err
		}
	}

	rwFs := fuse.NewLoopbackFileSystem(w.rwDir)

	ttl := 5.0
	opts := unionfs.UnionFsOptions{
		BranchCacheTTLSecs:   ttl,
		DeletionCacheTTLSecs: ttl,
		DeletionDirName:      _DELETIONS,
	}
	mOpts := fuse.FileSystemOptions{
		EntryTimeout:    ttl,
		AttrTimeout:     ttl,
		NegativeTimeout: 0.01,
	}

	w.unionFs = unionfs.NewUnionFs("ufs", []fuse.FileSystem{rwFs, me.rpcFs}, opts)
	swFs := []fuse.SwitchedFileSystem{
		{"dev", &DevNullFs{}, true},
		{"", me.rpcFs, false},

		// TODO - configurable.
		{"tmp", w.unionFs, false},
		{me.writableRoot, w.unionFs, false},
	}

	conn := fuse.NewFileSystemConnector(fuse.NewSwitchFileSystem(swFs), &mOpts)
	w.MountState = fuse.NewMountState(conn)

	fuseOpts := fuse.MountOptions{
		AllowOther: true,
		// Compilers are not that highly parallel.  A lower
		// number also helps stacktrace be less overwhelming.
		MaxBackground: 4,
	}
	w.MountState.Mount(w.mount, &fuseOpts)
	if err != nil {
		return nil, err
	}

	go w.MountState.Loop(true)

	return &w, nil
}

func (me *Mirror) newWorkerTask(req *WorkRequest, rep *WorkReply) (*WorkerTask, os.Error) {
	fuseFs, err := me.getWorkerFuseFs(fmt.Sprintf("%v", req))
	if err != nil {
		return nil, err
	}
	stdin := me.daemon.pending.WaitConnection(req.StdinId)
	return &WorkerTask{
		WorkRequest:  req,
		WorkReply:    rep,
		stdinConn:    stdin,
		masterWorker: me,
		fuseFs:       fuseFs,
	}, nil
}

func (me *Mirror) Status(req *StatusRequest, rep *StatusReply) os.Error {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()

	rep.Processes += len(me.workingFileSystems)
	return nil
}

func (me *Mirror) FileContent(req *ContentRequest, rep *ContentResponse) os.Error {
	return me.daemon.contentServer.FileContent(req, rep)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"
)

func Test_ExitOnWrongLoglevel(t *testing.T) {
	exitCode := -1
	osExitOriginal := osExit
	defer func() { osExit = osExitOriginal }()
	osExit = func(code int) {
		exitCode = code
	}
	originalArgs := os.Args
	os.Args = []string{"mailckd", "-log-level=FOOO"}
	defer func() { os.Args = originalArgs }()

	main()
	assert.Equal(t, 1, exitCode)
}

func Test_BasicEndToEnd(t *testing.T) {
	originalArgs := os.Args
	os.Args = []string{"mailckd", "-host=localhost", "-port=3000", "-text-logging=false"}
	defer func() { os.Args = originalArgs }()

	go main()

	time.Sleep(time.Second)

	r, err := http.Post("http://localhost:3000/api/validate", "application/x-www-form-urlencoded", strings.NewReader(`mail=foo@example.com`))
	assert.NoError(t, err)

	assert.Equal(t, 200, r.StatusCode)
	assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

	b, err := ioutil.ReadAll(r.Body)
	assert.NoError(t, err)

	result := map[string]interface{}{}
	err = json.Unmarshal(b, &result)
	assert.NoError(t, err)

	assert.Equal(t, "invalid", result["result"])
	assert.Equal(t, "invalidDomain", result["resultDetail"])
	assert.Equal(t, "The domain does not exist.", result["message"])
}
<commit_msg>fixed message in test<commit_after>package main

import (
	"encoding/json"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"
)

func Test_ExitOnWrongLoglevel(t *testing.T) {
	exitCode := -1
	osExitOriginal := osExit
	defer func() { osExit = osExitOriginal }()
	osExit = func(code int) {
		exitCode = code
	}
	originalArgs := os.Args
	os.Args = []string{"mailckd", "-log-level=FOOO"}
	defer func() { os.Args = originalArgs }()

	main()
	assert.Equal(t, 1, exitCode)
}

func Test_BasicEndToEnd(t *testing.T) {
	originalArgs := os.Args
	os.Args = []string{"mailckd", "-host=localhost", "-port=3002", "-text-logging=false"}
	defer func() { os.Args = originalArgs }()

	go main()

	time.Sleep(time.Second)

	r, err := http.Post("http://localhost:3002/api/validate", "application/x-www-form-urlencoded", strings.NewReader(`mail=foo@example.com`))
	assert.NoError(t, err)

	assert.Equal(t, 200, r.StatusCode)
	assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

	b, err := ioutil.ReadAll(r.Body)
	assert.NoError(t, err)

	result := map[string]interface{}{}
	err = json.Unmarshal(b, &result)
	assert.NoError(t, err)

	assert.Equal(t, "invalid", result["result"])
	assert.Equal(t, "invalidDomain", result["resultDetail"])
	assert.Equal(t, "The email domain does not exist.", result["message"])
}
<|endoftext|>


<commit_before>// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package images

import (
	"errors"
	"image"
	"sync"

	// Importing image codecs for image.DecodeConfig
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"

	// Import webp codec
	_ "golang.org/x/image/webp"

	"github.com/gohugoio/hugo/deps"
	"github.com/spf13/cast"
)

// New returns a new instance of the images-namespaced template functions.
func New(deps *deps.Deps) *Namespace {
	return &Namespace{
		cache: map[string]image.Config{},
		deps:  deps,
	}
}

// Namespace provides template functions for the "images" namespace.
type Namespace struct {
	cacheMu sync.RWMutex
	cache   map[string]image.Config

	deps *deps.Deps
}

// Config returns the image.Config for the specified path relative to the
// working directory.
func (ns *Namespace) Config(path interface{}) (image.Config, error) {
	filename, err := cast.ToStringE(path)
	if err != nil {
		return image.Config{}, err
	}

	if filename == "" {
		return image.Config{}, errors.New("config needs a filename")
	}

	// Check cache for image config.
	ns.cacheMu.RLock()
	config, ok := ns.cache[filename]
	ns.cacheMu.RUnlock()

	if ok {
		return config, nil
	}

	f, err := ns.deps.Fs.WorkingDir.Open(filename)
	if err != nil {
		return image.Config{}, err
	}

	config, _, err = image.DecodeConfig(f)
	if err != nil {
		return config, err
	}

	ns.cacheMu.Lock()
	ns.cache[filename] = config
	ns.cacheMu.Unlock()

	return config, nil
}
<commit_msg>tpl/images: Close image file<commit_after>// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package images

import (
	"errors"
	"image"
	"sync"

	// Importing image codecs for image.DecodeConfig
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"

	// Import webp codec
	_ "golang.org/x/image/webp"

	"github.com/gohugoio/hugo/deps"
	"github.com/spf13/cast"
)

// New returns a new instance of the images-namespaced template functions.
func New(deps *deps.Deps) *Namespace {
	return &Namespace{
		cache: map[string]image.Config{},
		deps:  deps,
	}
}

// Namespace provides template functions for the "images" namespace.
type Namespace struct {
	cacheMu sync.RWMutex
	cache   map[string]image.Config

	deps *deps.Deps
}

// Config returns the image.Config for the specified path relative to the
// working directory.
func (ns *Namespace) Config(path interface{}) (image.Config, error) {
	filename, err := cast.ToStringE(path)
	if err != nil {
		return image.Config{}, err
	}

	if filename == "" {
		return image.Config{}, errors.New("config needs a filename")
	}

	// Check cache for image config.
	ns.cacheMu.RLock()
	config, ok := ns.cache[filename]
	ns.cacheMu.RUnlock()

	if ok {
		return config, nil
	}

	f, err := ns.deps.Fs.WorkingDir.Open(filename)
	if err != nil {
		return image.Config{}, err
	}
	defer f.Close()

	config, _, err = image.DecodeConfig(f)
	if err != nil {
		return config, err
	}

	ns.cacheMu.Lock()
	ns.cache[filename] = config
	ns.cacheMu.Unlock()

	return config, nil
}
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Nan Deng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package msgcenter

import (
	"github.com/petar/GoLLRB/llrb"
	"github.com/uniqush/uniqush-conn/proto/server"
	"sync"
)

type ConnSet interface {
	NrConn() int
	Traverse(f func(c server.Conn) error) error
	CloseAll()
}

type connSet struct {
	mutex sync.RWMutex
	name  string
	list  []server.Conn
}

func (self *connSet) CloseAll() {
	if self == nil {
		return
	}
	self.mutex.Lock()
	for _, c := range self.list {
		c.Close()
	}
	self.list = nil
}

func (self *connSet) unlock() {
	self.mutex.Unlock()
}

// Never manipulate another connSet inside the function f.
// It may lead to deadlock.
// Only perform read-only operation (to the connSet, not the Conn) in the function f().
func (self *connSet) Traverse(f func(c server.Conn) error) error {
	if self == nil {
		return nil
	}
	self.mutex.RLock()
	defer self.mutex.RUnlock()

	if self == nil {
		return nil
	}

	for _, c := range self.list {
		err := f(c)
		if err != nil {
			return err
		}
	}
	return nil
}

func (self *connSet) lock() {
	self.mutex.Lock()
}

func (self *connSet) NrConn() int {
	if self == nil {
		return 0
	}
	self.mutex.Lock()
	defer self.mutex.Unlock()

	return self.nrConn()
}

func (self *connSet) nrConn() int {
	if self == nil {
		return 0
	}
	return len(self.list)
}

func (self *connSet) del(key string) server.Conn {
	var c server.Conn
	i := -1
	for i, c = range self.list {
		if c.UniqId() == key {
			break
		}
	}
	if i < 0 || i >= len(self.list) {
		return nil
	}
	c = self.list[i]
	self.list[i] = self.list[len(self.list)-1]
	self.list = self.list[:len(self.list)-1]

	return c
}

func (self *connSet) add(conn server.Conn, max int) error {
	for _, c := range self.list {
		if c.UniqId() == conn.UniqId() {
			return nil
		}
	}
	if max > 0 && len(self.list) >= max {
		return ErrTooManyConnForThisUser
	}
	self.list = append(self.list, conn)
	return nil
}

func (self *connSet) key() string {
	if len(self.list) == 0 {
		return self.name
	}
	return connKey(self.list[0])
}

func (self *connSet) Less(than llrb.Item) bool {
	if self == nil {
		return true
	}
	thanCs := than.(*connSet)
	if thanCs == nil {
		return false
	}
	selfKey := llrb.String(self.key())
	thanKey := llrb.String(than.(*connSet).key())
	return selfKey.Less(thanKey)
}

func connKey(conn server.Conn) string {
	return conn.Username()
}
<commit_msg>use rwmutex<commit_after>/*
 * Copyright 2013 Nan Deng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package msgcenter

import (
	"github.com/petar/GoLLRB/llrb"
	"github.com/uniqush/uniqush-conn/proto/server"
	"sync"
)

type ConnSet interface {
	NrConn() int
	Traverse(f func(c server.Conn) error) error
	CloseAll()
}

type connSet struct {
	mutex sync.RWMutex
	name  string
	list  []server.Conn
}

func (self *connSet) CloseAll() {
	if self == nil {
		return
	}
	self.mutex.Lock()
	for _, c := range self.list {
		c.Close()
	}
	self.list = nil
}

func (self *connSet) unlock() {
	self.mutex.Unlock()
}

// Never manipulate another connSet inside the function f.
// It may lead to deadlock.
// Only perform read-only operation (to the connSet, not the Conn) in the function f().
func (self *connSet) Traverse(f func(c server.Conn) error) error {
	if self == nil {
		return nil
	}
	self.mutex.RLock()
	defer self.mutex.RUnlock()

	if self == nil {
		return nil
	}

	for _, c := range self.list {
		err := f(c)
		if err != nil {
			return err
		}
	}
	return nil
}

func (self *connSet) lock() {
	self.mutex.Lock()
}

func (self *connSet) NrConn() int {
	if self == nil {
		return 0
	}
	self.mutex.RLock()
	defer self.mutex.RUnlock()

	return self.nrConn()
}

func (self *connSet) nrConn() int {
	if self == nil {
		return 0
	}
	return len(self.list)
}

func (self *connSet) del(key string) server.Conn {
	var c server.Conn
	i := -1
	for i, c = range self.list {
		if c.UniqId() == key {
			break
		}
	}
	if i < 0 || i >= len(self.list) {
		return nil
	}
	c = self.list[i]
	self.list[i] = self.list[len(self.list)-1]
	self.list = self.list[:len(self.list)-1]

	return c
}

func (self *connSet) add(conn server.Conn, max int) error {
	for _, c := range self.list {
		if c.UniqId() == conn.UniqId() {
			return nil
		}
	}
	if max > 0 && len(self.list) >= max {
		return ErrTooManyConnForThisUser
	}
	self.list = append(self.list, conn)
	return nil
}

func (self *connSet) key() string {
	if len(self.list) == 0 {
		return self.name
	}
	return connKey(self.list[0])
}

func (self *connSet) Less(than llrb.Item) bool {
	if self == nil {
		return true
	}
	thanCs := than.(*connSet)
	if thanCs == nil {
		return false
	}
	selfKey := llrb.String(self.key())
	thanKey := llrb.String(than.(*connSet).key())
	return selfKey.Less(thanKey)
}

func connKey(conn server.Conn) string {
	return conn.Username()
}
<|endoftext|>


<commit_before>package maps

// MultiHashMap XXX
type MultiHashMap struct {
	data map[interface{}][]interface{}
}

// Put XXX
func (m *MultiHashMap) Put(key interface{}, value interface{}) {
	values, ok := m.data[key]
	if ok {
		values = append(values, value)
	} else {
		values = []interface{}{value}
	}
	m.data[key] = values
}

// Get XXX
func (m *MultiHashMap) Get(key interface{}) (values []interface{}, ok bool) {
	values, ok = m.data[key]
	if !ok {
		return nil, false
	}
	return values, true
}

// Count XXX
func (m *MultiHashMap) Count(key interface{}) (count int, ok bool) {
	values, ok := m.data[key]
	if !ok {
		return 0, false
	}
	return len(values), true
}

// Has XXX
func (m *MultiHashMap) Has(key interface{}) bool {
	_, ok := m.data[key]
	return ok
}

// HasKeyValue XXX
func (m *MultiHashMap) HasKeyValue(key interface{}, value interface{}) bool {
	values, ok := m.data[key]
	if !ok {
		return false
	}
	for val := range values {
		if val == value {
			return true
		}
	}
	return false
}

// Del XXX
func (m *MultiHashMap) Del(key interface{}) {
	delete(m.data, key)
}

// DelKeyValue XXX
func (m *MultiHashMap) DelKeyValue(key interface{}, value interface{}) {
	values, ok := m.data[key]
	if ok {
		for i, val := range values {
			if val == value {
				values = append(values[:i], values[i+1:]...)
			}
		}
		m.data[key] = values
	}
}

// Keys XXX
func (m *MultiHashMap) Keys() []interface{} {
	res := make([]interface{}, len(m.data))
	i := 0
	for k := range m.data {
		res[i] = k
		i++
	}
	return res
}
<commit_msg>map: MultiHashMap quick win from codebeat (#31)<commit_after>package maps

// MultiHashMap XXX
type MultiHashMap struct {
	data map[interface{}][]interface{}
}

// Put XXX
func (m *MultiHashMap) Put(key interface{}, value interface{}) {
	values, ok := m.data[key]
	if ok {
		values = append(values, value)
	} else {
		values = []interface{}{value}
	}
	m.data[key] = values
}

// Get XXX
func (m *MultiHashMap) Get(key interface{}) (values []interface{}, ok bool) {
	values, ok = m.data[key]
	if !ok {
		return nil, false
	}
	return values, true
}

// Count XXX
func (m *MultiHashMap) Count(key interface{}) (count int, ok bool) {
	values, ok := m.data[key]
	if !ok {
		return 0, false
	}
	return len(values), true
}

// Has XXX
func (m *MultiHashMap) Has(key interface{}) bool {
	_, ok := m.data[key]
	return ok
}

// HasKeyValue XXX
func (m *MultiHashMap) HasKeyValue(key interface{}, value interface{}) bool {
	values, ok := m.data[key]
	if !ok {
		return false
	}
	for val := range values {
		if val == value {
			return true
		}
	}
	return false
}

// Del XXX
func (m *MultiHashMap) Del(key interface{}) {
	delete(m.data, key)
}

// DelKeyValue XXX
func (m *MultiHashMap) DelKeyValue(key interface{}, value interface{}) {
	values, ok := m.data[key]
	if !ok {
		return
	}
	for i, val := range values {
		if val == value {
			values = append(values[:i], values[i+1:]...)
		}
	}
	m.data[key] = values
}

// Keys XXX
func (m *MultiHashMap) Keys() []interface{} {
	res := make([]interface{}, len(m.data))
	i := 0
	for k := range m.data {
		res[i] = k
		i++
	}
	return res
}
<|endoftext|>


<commit_before>package tracer

import (
	"math"
)

type Primitive struct {
	Intersectable
	material Material
}

func (p Primitive) Material() Material {
	return p.material
}

type Plane struct {
	Primitive
	Normal Vector
	D      float64
}

func NewPlane(normal Vector, d float64, material Material) *Plane {
	p := Plane{
		Normal: normal.Normalize(),
		D:      d,
	}
	p.material = material
	return &p
}

func (p *Plane) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	t = (ray.Origin.Dot(p.Normal) + p.D) / ray.Direction.Dot(p.Normal)
	return t >= 0, t, p.Normal
}

type Triangle struct {
	Primitive
	P1, P2, P3 Vector
	Normal     Vector
}

func NewTriangle(p1, p2, p3 Vector, material Material) *Triangle {
	e1 := p2.Subtract(p1)
	e2 := p3.Subtract(p1)
	n := e1.Cross(e2).Normalize()

	t := Triangle{P1: p1, P2: p2, P3: p3, Normal: n}
	t.material = material
	return &t
}

const epsilon = 1e-8

//Intersect Intersects a ray with a triangle using the Möller–Trumbore algorithm
func (tr *Triangle) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	n = tr.Normal
	// if ray.Direction.Dot(n) < 0 {
	// 	n = n.Multiply(-1)
	// }

	t = 0
	e1 := tr.P2.Subtract(tr.P1)
	e2 := tr.P3.Subtract(tr.P1)

	pvec := ray.Direction.Cross(e2)
	det := e1.Dot(pvec)

	//ray is parallel to plane
	if det < epsilon && det > -epsilon {
		return false, t, n
	}

	invDet := 1 / det
	tvec := ray.Origin.Subtract(tr.P1)
	u := tvec.Dot(pvec) * invDet
	if u < 0 || u > 1 {
		return false, t, n
	}

	qvec := tvec.Cross(e1)
	v := ray.Direction.Dot(qvec) * invDet
	if v < 0 || u+v > 1 {
		return false, t, n
	}

	t = e2.Dot(qvec) * invDet
	return true, t, n
}

type Quad struct {
	Primitive
	P1, P2, P3, P4 Vector
	Normal         Vector
}

func NewQuad(p1, p2, p3, p4 Vector, material Material) *Quad {
	e1 := p2.Subtract(p1)
	e2 := p3.Subtract(p1)
	n := e1.Cross(e2).Normalize()
	q := Quad{P1: p1, P2: p2, P3: p3, P4: p4, Normal: n}
	q.material = material
	return &q
}

//todo don't use triangles
func (q *Quad) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	t1 := Triangle{P1: q.P1, P2: q.P2, P3: q.P4}
	t2 := Triangle{P1: q.P2, P2: q.P3, P3: q.P4}

	if intersected, t, n := t1.Intersect(ray); intersected {
		return intersected, t, n
	}

	if intersected, t, n := t2.Intersect(ray); intersected {
		return intersected, t, n
	}

	return false, t, n
}

type Sphere struct {
	Primitive
	Center       Vector
	Radius, rad2 float64
}

func NewSphere(center Vector, radius float64, material Material) *Sphere {
	s := Sphere{Center: center, Radius: radius, rad2: radius * radius}
	s.material = material
	return &s
}

func (s *Sphere) Intersect(ray Ray) (intersected bool, t float64, n Vector) {

	m := ray.Origin.Subtract(s.Center)
	b := m.Dot(ray.Direction)
	c := m.Dot(m) - s.rad2

	if c > 0 && b > 0 {
		return false, t, n
	}

	discr := b*b - c

	if discr < 0 {
		return false, t, n
	}

	t = -b - math.Sqrt(discr)
	if t < 0 {
		t = 0
	}
	n = ray.Point(t).Subtract(s.Center).Normalize()
	return true, t, n

}

// //fast way to calculate
// t = Vector3.Dot(-C, ray.Direction);
// var q = -C - t*ray.Direction;
// float p2 = Vector3.Dot(q, q);
// if (p2 > _rad2) return false;
// t -= (float) Math.Sqrt(_rad2 - p2);
<commit_msg>Alternative sphere intersection<commit_after>package tracer

import (
	"math"
)

type Primitive struct {
	Intersectable
	material Material
}

func (p Primitive) Material() Material {
	return p.material
}

type Plane struct {
	Primitive
	Normal Vector
	D      float64
}

func NewPlane(normal Vector, d float64, material Material) *Plane {
	p := Plane{
		Normal: normal.Normalize(),
		D:      d,
	}
	p.material = material
	return &p
}

func (p *Plane) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	t = (ray.Origin.Dot(p.Normal) + p.D) / ray.Direction.Dot(p.Normal)
	return t >= 0, t, p.Normal
}

type Triangle struct {
	Primitive
	P1, P2, P3 Vector
	Normal     Vector
}

func NewTriangle(p1, p2, p3 Vector, material Material) *Triangle {
	e1 := p2.Subtract(p1)
	e2 := p3.Subtract(p1)
	n := e1.Cross(e2).Normalize()

	t := Triangle{P1: p1, P2: p2, P3: p3, Normal: n}
	t.material = material
	return &t
}

const epsilon = 1e-8

//Intersect Intersects a ray with a triangle using the Möller–Trumbore algorithm
func (tr *Triangle) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	n = tr.Normal
	// if ray.Direction.Dot(n) < 0 {
	// 	n = n.Multiply(-1)
	// }

	t = 0
	e1 := tr.P2.Subtract(tr.P1)
	e2 := tr.P3.Subtract(tr.P1)

	pvec := ray.Direction.Cross(e2)
	det := e1.Dot(pvec)

	//ray is parallel to plane
	if det < epsilon && det > -epsilon {
		return false, t, n
	}

	invDet := 1 / det
	tvec := ray.Origin.Subtract(tr.P1)
	u := tvec.Dot(pvec) * invDet
	if u < 0 || u > 1 {
		return false, t, n
	}

	qvec := tvec.Cross(e1)
	v := ray.Direction.Dot(qvec) * invDet
	if v < 0 || u+v > 1 {
		return false, t, n
	}

	t = e2.Dot(qvec) * invDet
	return true, t, n
}

type Quad struct {
	Primitive
	P1, P2, P3, P4 Vector
	Normal         Vector
}

func NewQuad(p1, p2, p3, p4 Vector, material Material) *Quad {
	e1 := p2.Subtract(p1)
	e2 := p3.Subtract(p1)
	n := e1.Cross(e2).Normalize()
	q := Quad{P1: p1, P2: p2, P3: p3, P4: p4, Normal: n}
	q.material = material
	return &q
}

//todo don't use triangles
func (q *Quad) Intersect(ray Ray) (intersected bool, t float64, n Vector) {
	t1 := Triangle{P1: q.P1, P2: q.P2, P3: q.P4}
	t2 := Triangle{P1: q.P2, P2: q.P3, P3: q.P4}

	if intersected, t, n := t1.Intersect(ray); intersected {
		return intersected, t, n
	}

	if intersected, t, n := t2.Intersect(ray); intersected {
		return intersected, t, n
	}

	return false, t, n
}

type Sphere struct {
	Primitive
	Center       Vector
	Radius, rad2 float64
}

func NewSphere(center Vector, radius float64, material Material) *Sphere {
	s := Sphere{Center: center, Radius: radius, rad2: radius * radius}
	s.material = material
	return &s
}

func (s *Sphere) Intersect(ray Ray) (intersected bool, t float64, n Vector) {

	// m := ray.Origin.Subtract(s.Center)
	// b := m.Dot(ray.Direction)
	// c := m.Dot(m) - s.rad2

	// if c > 0 && b > 0 {
	// 	return false, t, n
	// }

	// discr := b*b - c

	// if discr < 0 {
	// 	return false, t, n
	// }

	// t = -b - math.Sqrt(discr)
	// if t < 0 {
	// 	t = 0
	// }
	// n = ray.Point(t).Subtract(s.Center).Normalize()
	// return true, t, n

	C := ray.Origin.Subtract(s.Center)

	a := ray.Direction.Dot(ray.Direction)
	b := ray.Direction.Dot(C) * 2
	c := C.Dot(C) - s.rad2
	d := b*b - 4*a*c

	if d < 0 {
		return false, t, n
	}

	sd := math.Sqrt(d)
	t1 := (-b + sd) / (2 * a)
	t2 := (-b - sd) / (2 * a)
	t = math.Max(t1, t2)
	n = ray.Point(t1).Subtract(s.Center).Normalize()

	return t > 0, t, n
	// var a = Vector3.Dot(ray.Direction, ray.Direction);
	//                 var b = Vector3.Dot(2*ray.Direction, C);
	//                 var c = dotC - _rad2;
	//                 var d = b*b - 4*a*c;

	//                 //no intersection
	//                 if (d < 0)
	//                 {
	//                     return false;
	//                 }

	//                 var sd = (float)Math.Sqrt(d);
	//                 var t1 = (-b + sd)/2*a;
	//                 var t2 = (-b - sd)/2*a;
	//                 t = Math.Max(t1, t2);
}

// //fast way to calculate
// t = Vector3.Dot(-C, ray.Direction);
// var q = -C - t*ray.Direction;
// float p2 = Vector3.Dot(q, q);
// if (p2 > _rad2) return false;
// t -= (float) Math.Sqrt(_rad2 - p2);
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/tidwall/gjson"
)

var videosMD = "content/page/videos.md"
var videosNoYoutube = "scripts/videosNoYoutube.txt"
var videosLanding = "content/post/vigotech-charlas.md"
var projectsMD = "content/page/proxectos.md"
var projectsLanding = "content/post/vigotech-proxectos.md"
var channelJSON = "scripts/channels.json"
var projectsJSON = "scripts/projects.json"

type channelType []struct {
	ChannelName string `json:"channelName"`
	ChannelID   string `json:"channelID"`
}

var channels = channelType{}

type video struct {
	title, videoID, channel, publishedAt string
}

type videosType []video

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

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

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

var videos = videosType{}

type projectsType []struct {
	User string `json:"user"`
	Repo string `json:"repo"`
}

var projects = projectsType{}

func main() {

	token := os.Getenv("YOUTUBE_TOKEN")
	now := time.Now().Format(time.RFC3339)

	// Read JSON channels
	c, err6 := os.Open(channelJSON)
	if err6 != nil {
		log.Fatal("Not able to open ", channelJSON)
	}

	jsonParser := json.NewDecoder(c)
	if err6 = jsonParser.Decode(&channels); err6 != nil {
		log.Fatal("Parsing channels JSONfile", err6.Error())
	}

	// Read JSON channels
	c1, err7 := os.Open(projectsJSON)
	if err7 != nil {
		log.Fatal("Not able to open ", projectsJSON)
	}

	jsonParser = json.NewDecoder(c1)
	if err7 = jsonParser.Decode(&projects); err7 != nil {
		log.Fatal("Parsing projects JSON file", err7.Error())
	}

	// Prepare Videos Markdown page
	_ = os.Truncate(videosMD, 0)
	var file, err = os.OpenFile(videosMD, os.O_RDWR, 0644)
	if err != nil {
		log.Fatal("Not able to open ", videosMD)
	}
	defer file.Close()

	s := `+++
date = "$now"
draft = false
title = "Charlas"
type = "page"
weight = 1
+++

----
Charlas gravadas polos distintos grupos:

`

	file.WriteString(strings.Replace(s, "$now", now, 1))

	for _, c := range channels {
		// Processing YouTube Requests
		url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/search?"+
			"key=%s&channelId=%s&part=snippet,id&order=date&maxResults=50",
			token, c.ChannelID)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			log.Fatal("NewRequest: ", err)
			return
		}

		client := &http.Client{}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal("Do: ", err)
			return
		}

		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			log.Fatal("YouTube response is: ", resp.StatusCode)
		}

		bodyBytes, _ := ioutil.ReadAll(resp.Body)
		record := string(bodyBytes)

		totalResults := gjson.Get(record, "pageInfo.totalResults")

		if totalResults.Int() > 49 {
			log.Fatal("Returned 50 videos, we could loose records")
		}
		s1 := fmt.Sprintf(
			"\n\n### [%s](https://www.youtube.com/channel/%s)\n\n",
			c.ChannelName,
			c.ChannelID)
		file.WriteString(s1)

		r := gjson.Get(record, "items")
		for _, item := range r.Array() {

			kind := gjson.Get(item.String(), "id.kind")

			if kind.String() != "youtube#video" {
				continue
			}

			title := gjson.Get(item.String(), "snippet.title").String()
			videoID := gjson.Get(item.String(), "id.videoId").String()
			publishedAt := gjson.Get(
				item.String(),
				"snippet.publishedAt").String()

			s2 := fmt.Sprintf("- [%s](https://www.youtube.com/watch?v=%s)\n",
				title, videoID)
			file.WriteString(s2)

			videos = append(videos, video{
				title:       title,
				videoID:     videoID,
				channel:     c.ChannelName,
				publishedAt: publishedAt,
			})
		}
	}

	// Add videos which aren't hosted in YouTube
	dat, err := ioutil.ReadFile(videosNoYoutube)
	if err != nil {
		log.Fatal("Not able to open ", videosNoYoutube)
	}
	file.WriteString(string(dat))

	// Update the videos landing page
	_ = os.Truncate(videosLanding, 0)
	var fileLanding, err3 = os.OpenFile(videosLanding, os.O_RDWR, 0644)
	if err3 != nil {
		log.Fatal("Not able to open ", videosLanding)
	}
	defer fileLanding.Close()

	s = `+++
date = "$now"
draft = false
title = "Charlas"
weight = 102
type = "post"
class="post last talks"

+++

A maioría de charlas está gravadas e dispoñibles para o seu visionado.
Están son as catro últimas:

`
	fileLanding.WriteString(strings.Replace(s, "$now", now, 1))

	sort.Sort(sort.Reverse(videos))

	for i, v := range videos {

		s = fmt.Sprintf("* [%s](https://www.youtube.com/watch?v=%s) (%s)\n",
			v.title,
			v.videoID,
			v.channel)

		//fileLanding.WriteString(s)

		s = fmt.Sprintf("
"+
			"

\n",
			v.videoID)

		fileLanding.WriteString(s)

		if i == 3 {
			break
		}
	}
	s = "\n\n

\n\n"
	fileLanding.WriteString(s)

	s = `
[Preme aquí para ver tódalas charlas](./page/videos/)
`

	fileLanding.WriteString(s)

	// Prepare Proxectos file output
	_ = os.Truncate(projectsMD, 0)
	var fileProjects, err4 = os.OpenFile(projectsMD, os.O_RDWR, 0644)
	if err4 != nil {
		log.Fatal("Not able to open ", projectsMD)
	}
	defer fileProjects.Close()

	s = `+++
date = "$now"
draft = false
title = "Proxectos"
type = "page"
weight = 1
+++

----
Proxectos de código aberto creados por xente da comunidade:

`

	fileProjects.WriteString(strings.Replace(s, "$now", now, 1))

	fileProjects.WriteString("
\n\n")

	for i, v := range projects {

		if i%2 == 0 {
			fileProjects.WriteString("
\n")
		}

		s = fmt.Sprintf("\n
"+
			"

\n",
			v.User,
			v.Repo)

		fileProjects.WriteString(s)

		if i%2 == 1 || len(projects)-1 == i {
			fileProjects.WriteString("
\n")
		}
	}
	fileProjects.WriteString("
\n\n")

	fileProjects.WriteString("")

	// Update the projects landing page
	_ = os.Truncate(projectsLanding, 0)
	var fileProjectsLanding, err5 = os.OpenFile(
		projectsLanding, os.O_RDWR, 0644)
	if err5 != nil {
		log.Fatal("Not able to open ", projectsLanding)
	}
	defer fileProjectsLanding.Close()

	s = `+++
date = "$now"
draft = true
title = "Proxectos"
weight = 103
type = "post"
class="post last projects"

+++

Proxectos de código aberto creados por xente da comunidade:
`
	fileProjectsLanding.WriteString(strings.Replace(s, "$now", now, 1))

	rand.Seed(time.Now().Unix())
	random := rand.Int()

	fileProjectsLanding.WriteString("
\n\n")

	for i := 0; i < 6; i++ {

		if i%2 == 0 {
			fileProjectsLanding.WriteString("
\n")
		}

		n := (random + i) % len(projects)
		s = fmt.Sprintf("\n
"+
			"

\n",
			projects[n].User,
			projects[n].Repo)

		fileProjectsLanding.WriteString(s)

		if i%2 == 1 {
			fileProjectsLanding.WriteString("
\n")
		}
	}

	fileProjectsLanding.WriteString("
\n\n")

	s = `

[Preme aquí para ver tódolos proxectos](./page/proxectos/)

`
	fileProjectsLanding.WriteString(s)

}
<commit_msg>Recover projects<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/tidwall/gjson"
)

var videosMD = "content/page/videos.md"
var videosNoYoutube = "scripts/videosNoYoutube.txt"
var videosLanding = "content/post/vigotech-charlas.md"
var projectsMD = "content/page/proxectos.md"
var projectsLanding = "content/post/vigotech-proxectos.md"
var channelJSON = "scripts/channels.json"
var projectsJSON = "scripts/projects.json"

type channelType []struct {
	ChannelName string `json:"channelName"`
	ChannelID   string `json:"channelID"`
}

var channels = channelType{}

type video struct {
	title, videoID, channel, publishedAt string
}

type videosType []video

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

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

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

var videos = videosType{}

type projectsType []struct {
	User string `json:"user"`
	Repo string `json:"repo"`
}

var projects = projectsType{}

func main() {

	token := os.Getenv("YOUTUBE_TOKEN")
	now := time.Now().Format(time.RFC3339)

	// Read JSON channels
	c, err6 := os.Open(channelJSON)
	if err6 != nil {
		log.Fatal("Not able to open ", channelJSON)
	}

	jsonParser := json.NewDecoder(c)
	if err6 = jsonParser.Decode(&channels); err6 != nil {
		log.Fatal("Parsing channels JSONfile", err6.Error())
	}

	// Read JSON channels
	c1, err7 := os.Open(projectsJSON)
	if err7 != nil {
		log.Fatal("Not able to open ", projectsJSON)
	}

	jsonParser = json.NewDecoder(c1)
	if err7 = jsonParser.Decode(&projects); err7 != nil {
		log.Fatal("Parsing projects JSON file", err7.Error())
	}

	// Prepare Videos Markdown page
	_ = os.Truncate(videosMD, 0)
	var file, err = os.OpenFile(videosMD, os.O_RDWR, 0644)
	if err != nil {
		log.Fatal("Not able to open ", videosMD)
	}
	defer file.Close()

	s := `+++
date = "$now"
draft = false
title = "Charlas"
type = "page"
weight = 1
+++

----
Charlas gravadas polos distintos grupos:

`

	file.WriteString(strings.Replace(s, "$now", now, 1))

	for _, c := range channels {
		// Processing YouTube Requests
		url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/search?"+
			"key=%s&channelId=%s&part=snippet,id&order=date&maxResults=50",
			token, c.ChannelID)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			log.Fatal("NewRequest: ", err)
			return
		}

		client := &http.Client{}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal("Do: ", err)
			return
		}

		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			log.Fatal("YouTube response is: ", resp.StatusCode)
		}

		bodyBytes, _ := ioutil.ReadAll(resp.Body)
		record := string(bodyBytes)

		totalResults := gjson.Get(record, "pageInfo.totalResults")

		if totalResults.Int() > 49 {
			log.Fatal("Returned 50 videos, we could loose records")
		}
		s1 := fmt.Sprintf(
			"\n\n### [%s](https://www.youtube.com/channel/%s)\n\n",
			c.ChannelName,
			c.ChannelID)
		file.WriteString(s1)

		r := gjson.Get(record, "items")
		for _, item := range r.Array() {

			kind := gjson.Get(item.String(), "id.kind")

			if kind.String() != "youtube#video" {
				continue
			}

			title := gjson.Get(item.String(), "snippet.title").String()
			videoID := gjson.Get(item.String(), "id.videoId").String()
			publishedAt := gjson.Get(
				item.String(),
				"snippet.publishedAt").String()

			s2 := fmt.Sprintf("- [%s](https://www.youtube.com/watch?v=%s)\n",
				title, videoID)
			file.WriteString(s2)

			videos = append(videos, video{
				title:       title,
				videoID:     videoID,
				channel:     c.ChannelName,
				publishedAt: publishedAt,
			})
		}
	}

	// Add videos which aren't hosted in YouTube
	dat, err := ioutil.ReadFile(videosNoYoutube)
	if err != nil {
		log.Fatal("Not able to open ", videosNoYoutube)
	}
	file.WriteString(string(dat))

	// Update the videos landing page
	_ = os.Truncate(videosLanding, 0)
	var fileLanding, err3 = os.OpenFile(videosLanding, os.O_RDWR, 0644)
	if err3 != nil {
		log.Fatal("Not able to open ", videosLanding)
	}
	defer fileLanding.Close()

	s = `+++
date = "$now"
draft = false
title = "Charlas"
weight = 102
type = "post"
class="post last talks"

+++

A maioría de charlas está gravadas e dispoñibles para o seu visionado.
Están son as catro últimas:

`
	fileLanding.WriteString(strings.Replace(s, "$now", now, 1))

	sort.Sort(sort.Reverse(videos))

	for i, v := range videos {

		s = fmt.Sprintf("* [%s](https://www.youtube.com/watch?v=%s) (%s)\n",
			v.title,
			v.videoID,
			v.channel)

		//fileLanding.WriteString(s)

		s = fmt.Sprintf("
"+
			"

\n",
			v.videoID)

		fileLanding.WriteString(s)

		if i == 3 {
			break
		}
	}
	s = "\n\n

\n\n"
	fileLanding.WriteString(s)

	s = `
[Preme aquí para ver tódalas charlas](./page/videos/)
`

	fileLanding.WriteString(s)

	// Prepare Proxectos file output
	_ = os.Truncate(projectsMD, 0)
	var fileProjects, err4 = os.OpenFile(projectsMD, os.O_RDWR, 0644)
	if err4 != nil {
		log.Fatal("Not able to open ", projectsMD)
	}
	defer fileProjects.Close()

	s = `+++
date = "$now"
draft = false
title = "Proxectos"
type = "page"
weight = 1
+++

----
Proxectos de código aberto creados por xente da comunidade:

`

	fileProjects.WriteString(strings.Replace(s, "$now", now, 1))

	fileProjects.WriteString("
\n\n")

	for i, v := range projects {

		if i%2 == 0 {
			fileProjects.WriteString("
\n")
		}

		s = fmt.Sprintf("\n
"+
			"

\n",
			v.User,
			v.Repo)

		fileProjects.WriteString(s)

		if i%2 == 1 || len(projects)-1 == i {
			fileProjects.WriteString("
\n")
		}
	}
	fileProjects.WriteString("
\n\n")

	fileProjects.WriteString("")

	// Update the projects landing page
	_ = os.Truncate(projectsLanding, 0)
	var fileProjectsLanding, err5 = os.OpenFile(
		projectsLanding, os.O_RDWR, 0644)
	if err5 != nil {
		log.Fatal("Not able to open ", projectsLanding)
	}
	defer fileProjectsLanding.Close()

	s = `+++
date = "$now"
draft = false
title = "Proxectos"
weight = 103
type = "post"
class="post last projects"

+++

Proxectos de código aberto creados por xente da comunidade:
`
	fileProjectsLanding.WriteString(strings.Replace(s, "$now", now, 1))

	rand.Seed(time.Now().Unix())
	random := rand.Int()

	fileProjectsLanding.WriteString("
\n\n")

	for i := 0; i < 6; i++ {

		if i%2 == 0 {
			fileProjectsLanding.WriteString("
\n")
		}

		n := (random + i) % len(projects)
		s = fmt.Sprintf("\n
"+
			"

\n",
			projects[n].User,
			projects[n].Repo)

		fileProjectsLanding.WriteString(s)

		if i%2 == 1 {
			fileProjectsLanding.WriteString("
\n")
		}
	}

	fileProjectsLanding.WriteString("
\n\n")

	s = `

[Preme aquí para ver tódolos proxectos](./page/proxectos/)

`
	fileProjectsLanding.WriteString(s)

}
<|endoftext|>