<commit_before>package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strconv"

	"github.com/jellevandenhooff/keytree/crypto"
	"github.com/jellevandenhooff/keytree/wire"
)

var ErrExpectedNameOrHash = errors.New("expected name or hash in query")

func parseNameOrHash(r *http.Request) (crypto.Hash, error) {
	hashString := r.URL.Query().Get("hash")
	nameString := r.URL.Query().Get("name")

	if hashString != "" && nameString != "" {
		return crypto.EmptyHash, errors.New("expected either name or hash in query; not both")
	}

	if hashString == "" && nameString == "" {
		return crypto.EmptyHash, ErrExpectedNameOrHash
	}

	if hashString != "" {
		return crypto.HashFromString(hashString)
	}
	return crypto.HashString(nameString), nil
}

func replyJSON(w http.ResponseWriter, v interface{}) {
	bytes, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(bytes)
}

func (s *Server) handleTrieNode(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := crypto.HashFromString(r.URL.Query().Get("hash"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	node := s.dedup.FindAndDoNotAdd(hash)

	if node == nil {
		replyJSON(w, nil)
		return
	}

	if node.Entry != nil {
		replyJSON(w, &wire.TrieNode{
			Leaf: node.Entry,
		})
	} else {
		replyJSON(w, &wire.TrieNode{
			ChildHashes: &[2]crypto.Hash{node.Children[0].Hash(), node.Children[1].Hash()},
		})
	}
}

func (s *Server) handleLookup(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	update, err := s.db.Read(hash)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var entry *wire.Entry
	if update != nil {
		entry = update.Entry
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	lookups := make(map[string]*wire.SignedTrieLookup)

	for publicKey, trie := range s.allTries {
		lookup, leaf := trie.root.Lookup(hash)
		var leafHash crypto.Hash
		if leaf != nil {
			leafHash = leaf.EntryHash
		}

		if leafHash == entry.Hash() {
			lookups[publicKey] = &wire.SignedTrieLookup{
				SignedRoot: trie.signedRoot,
				TrieLookup: lookup,
			}
		}
	}

	replyJSON(w, &wire.LookupReply{
		Entry:             entry,
		SignedTrieLookups: lookups,
	})
}

func (s *Server) handleBrowse(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err == ErrExpectedNameOrHash {
		hash = crypto.EmptyHash
	} else if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var entries []*wire.Entry

	s.mu.Lock()
	root := s.localTrie.root
	s.mu.Unlock()

	for i := 0; i < 10; i++ {
		leaf := root.NextLeaf(hash)
		if leaf == nil {
			break
		}

		update, err := s.db.Read(leaf.NameHash)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		entries = append(entries, update.Entry)
		hash = leaf.NameHash
	}

	replyJSON(w, entries)
}

func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	log.Println(hash)

	sinceString := r.URL.Query().Get("since")
	var since uint64
	if sinceString != "" {
		sinceInt, err := strconv.Atoi(sinceString)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		since = uint64(sinceInt)
	} else {
		since = 0
	}

	update, err := s.db.ReadSince(hash, since)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	replyJSON(w, update)
}

func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
	}

	/*
		replyJSON(w, &Status{
			PublicKey:  s.config.PublicKey,
			Upstream:   s.config.Upstream,
			TotalNodes: s.dedup.NumNodes(),
		})
	*/
}

func (s *Server) handleSubmit(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	var update *wire.SignedEntry
	if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if err := update.Check(); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	err := s.doUpdate(update)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	replyJSON(w, err)
}

func (s *Server) handleUpdateBatch(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := crypto.HashFromString(r.URL.Query().Get("hash"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	batch, ok := s.updateCache.get(hash)
	if !ok {
		replyJSON(w, nil)
	}

	replyJSON(w, batch)
}

func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	s.mu.Lock()
	defer s.mu.Unlock()

	replyJSON(w, s.localTrie.signedRoot)
}

func (s *Server) addHandlers(mux *http.ServeMux) {
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		s.handleIndex(w, r)
	})

	mux.HandleFunc("/lookup", func(w http.ResponseWriter, r *http.Request) {
		s.handleLookup(w, r)
	})

	mux.HandleFunc("/updatebatch", func(w http.ResponseWriter, r *http.Request) {
		s.handleUpdateBatch(w, r)
	})

	mux.HandleFunc("/root", func(w http.ResponseWriter, r *http.Request) {
		s.handleRoot(w, r)
	})

	mux.HandleFunc("/trienode", func(w http.ResponseWriter, r *http.Request) {
		s.handleTrieNode(w, r)
	})

	mux.HandleFunc("/history", func(w http.ResponseWriter, r *http.Request) {
		s.handleHistory(w, r)
	})

	mux.HandleFunc("/browse", func(w http.ResponseWriter, r *http.Request) {
		s.handleBrowse(w, r)
	})

	mux.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
		s.handleSubmit(w, r)
	})
}
<commit_msg>missing return<commit_after>package main

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"

	"github.com/jellevandenhooff/keytree/crypto"
	"github.com/jellevandenhooff/keytree/wire"
)

var ErrExpectedNameOrHash = errors.New("expected name or hash in query")

func parseNameOrHash(r *http.Request) (crypto.Hash, error) {
	hashString := r.URL.Query().Get("hash")
	nameString := r.URL.Query().Get("name")

	if hashString != "" && nameString != "" {
		return crypto.EmptyHash, errors.New("expected either name or hash in query; not both")
	}

	if hashString == "" && nameString == "" {
		return crypto.EmptyHash, ErrExpectedNameOrHash
	}

	if hashString != "" {
		return crypto.HashFromString(hashString)
	}
	return crypto.HashString(nameString), nil
}

func replyJSON(w http.ResponseWriter, v interface{}) {
	bytes, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(bytes)
}

func (s *Server) handleTrieNode(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := crypto.HashFromString(r.URL.Query().Get("hash"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	node := s.dedup.FindAndDoNotAdd(hash)

	if node == nil {
		replyJSON(w, nil)
		return
	}

	if node.Entry != nil {
		replyJSON(w, &wire.TrieNode{
			Leaf: node.Entry,
		})
	} else {
		replyJSON(w, &wire.TrieNode{
			ChildHashes: &[2]crypto.Hash{node.Children[0].Hash(), node.Children[1].Hash()},
		})
	}
}

func (s *Server) handleLookup(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	update, err := s.db.Read(hash)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var entry *wire.Entry
	if update != nil {
		entry = update.Entry
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	lookups := make(map[string]*wire.SignedTrieLookup)

	for publicKey, trie := range s.allTries {
		lookup, leaf := trie.root.Lookup(hash)
		var leafHash crypto.Hash
		if leaf != nil {
			leafHash = leaf.EntryHash
		}

		if leafHash == entry.Hash() {
			lookups[publicKey] = &wire.SignedTrieLookup{
				SignedRoot: trie.signedRoot,
				TrieLookup: lookup,
			}
		}
	}

	replyJSON(w, &wire.LookupReply{
		Entry:             entry,
		SignedTrieLookups: lookups,
	})
}

func (s *Server) handleBrowse(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err == ErrExpectedNameOrHash {
		hash = crypto.EmptyHash
	} else if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var entries []*wire.Entry

	s.mu.Lock()
	root := s.localTrie.root
	s.mu.Unlock()

	for i := 0; i < 10; i++ {
		leaf := root.NextLeaf(hash)
		if leaf == nil {
			break
		}

		update, err := s.db.Read(leaf.NameHash)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		entries = append(entries, update.Entry)
		hash = leaf.NameHash
	}

	replyJSON(w, entries)
}

func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := parseNameOrHash(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	sinceString := r.URL.Query().Get("since")
	var since uint64
	if sinceString != "" {
		sinceInt, err := strconv.Atoi(sinceString)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		since = uint64(sinceInt)
	} else {
		since = 0
	}

	update, err := s.db.ReadSince(hash, since)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	replyJSON(w, update)
}

func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
	}

	/*
		replyJSON(w, &Status{
			PublicKey:  s.config.PublicKey,
			Upstream:   s.config.Upstream,
			TotalNodes: s.dedup.NumNodes(),
		})
	*/
}

func (s *Server) handleSubmit(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	var update *wire.SignedEntry
	if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if err := update.Check(); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	err := s.doUpdate(update)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	replyJSON(w, err)
}

func (s *Server) handleUpdateBatch(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	hash, err := crypto.HashFromString(r.URL.Query().Get("hash"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	batch, ok := s.updateCache.get(hash)
	if !ok {
		replyJSON(w, nil)
		return
	}

	replyJSON(w, batch)
}

func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	s.mu.Lock()
	defer s.mu.Unlock()

	replyJSON(w, s.localTrie.signedRoot)
}

func (s *Server) addHandlers(mux *http.ServeMux) {
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		s.handleIndex(w, r)
	})

	mux.HandleFunc("/lookup", func(w http.ResponseWriter, r *http.Request) {
		s.handleLookup(w, r)
	})

	mux.HandleFunc("/updatebatch", func(w http.ResponseWriter, r *http.Request) {
		s.handleUpdateBatch(w, r)
	})

	mux.HandleFunc("/root", func(w http.ResponseWriter, r *http.Request) {
		s.handleRoot(w, r)
	})

	mux.HandleFunc("/trienode", func(w http.ResponseWriter, r *http.Request) {
		s.handleTrieNode(w, r)
	})

	mux.HandleFunc("/history", func(w http.ResponseWriter, r *http.Request) {
		s.handleHistory(w, r)
	})

	mux.HandleFunc("/browse", func(w http.ResponseWriter, r *http.Request) {
		s.handleBrowse(w, r)
	})

	mux.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
		s.handleSubmit(w, r)
	})
}
<|endoftext|>


<commit_before>package integration

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	AWSAccessKeyID           string
	AWSSecretAccessKey       string
	AWSRegion                string
	GCPServiceAccountKeyPath string
	GCPProjectID             string
	GCPRegion                string
	GCPZone                  string
	GCPEnvPrefix             string
	StateFileDir             string
	StemcellPath             string
	GardenReleasePath        string
	ConcourseReleasePath     string
	ConcourseDeploymentPath  string
	EnableTerraformFlag      bool
}

func LoadConfig() (Config, error) {
	config := loadConfigFromEnvVars()

	awsCredsValidationError := validateAWSCreds(config)
	gcpCredsValidationError := validateGCPCreds(config)

	if awsCredsValidationError == nil && gcpCredsValidationError == nil {
		return Config{}, errors.New("Multiple IAAS Credentials provided:\n Provide a set of credentials for a single IAAS.")
	}

	if awsCredsValidationError != nil && gcpCredsValidationError != nil {
		return Config{}, fmt.Errorf("Multiple Credential Errors Found: %s\nProvide a full set of credentials for a single IAAS.", gcpCredsValidationError)
	}

	if config.StateFileDir == "" {
		dir, err := ioutil.TempDir("", "")
		if err != nil {
			return Config{}, err
		}
		config.StateFileDir = dir
	}

	return config, nil
}

func validateAWSCreds(config Config) error {
	if config.AWSAccessKeyID == "" {
		return errors.New("aws access key id is missing")
	}

	if config.AWSSecretAccessKey == "" {
		return errors.New("aws secret access key is missing")
	}

	if config.AWSRegion == "" {
		return errors.New("aws region is missing")
	}

	return nil
}

func validateGCPCreds(config Config) error {
	if config.GCPServiceAccountKeyPath == "" {
		return errors.New("gcp service account key path is missing")
	}

	if config.GCPProjectID == "" {
		return errors.New("project id is missing")
	}

	if config.GCPRegion == "" {
		return errors.New("gcp region is missing")
	}

	if config.GCPZone == "" {
		return errors.New("gcp zone is missing")
	}

	return nil
}

func loadConfigFromEnvVars() Config {
	return Config{
		AWSAccessKeyID:           os.Getenv("AWS_ACCESS_KEY_ID"),
		AWSSecretAccessKey:       os.Getenv("AWS_SECRET_ACCESS_KEY"),
		AWSRegion:                os.Getenv("AWS_REGION"),
		GCPServiceAccountKeyPath: os.Getenv("BBL_GCP_SERVICE_ACCOUNT_KEY"),
		GCPProjectID:             os.Getenv("BBL_GCP_PROJECT_ID"),
		GCPRegion:                os.Getenv("BBL_GCP_REGION"),
		GCPZone:                  os.Getenv("BBL_GCP_ZONE"),
		GCPEnvPrefix:             os.Getenv("BBL_GCP_ENV_PREFIX"),
		StateFileDir:             os.Getenv("BBL_STATE_DIR"),
		StemcellPath:             os.Getenv("STEMCELL_PATH"),
		GardenReleasePath:        os.Getenv("GARDEN_RELEASE_PATH"),
		ConcourseReleasePath:     os.Getenv("CONCOURSE_RELEASE_PATH"),
		ConcourseDeploymentPath:  os.Getenv("CONCOURSE_DEPLOYMENT_PATH"),
		EnableTerraformFlag:      os.Getenv("ENABLE_TERRAFORM_FLAG") == "true",
	}
}
<commit_msg>bbl integration test env vars prefixed with BBL.<commit_after>package integration

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	AWSAccessKeyID           string
	AWSSecretAccessKey       string
	AWSRegion                string
	GCPServiceAccountKeyPath string
	GCPProjectID             string
	GCPRegion                string
	GCPZone                  string
	GCPEnvPrefix             string
	StateFileDir             string
	StemcellPath             string
	GardenReleasePath        string
	ConcourseReleasePath     string
	ConcourseDeploymentPath  string
	EnableTerraformFlag      bool
}

func LoadConfig() (Config, error) {
	config := loadConfigFromEnvVars()

	awsCredsValidationError := validateAWSCreds(config)
	gcpCredsValidationError := validateGCPCreds(config)

	if awsCredsValidationError == nil && gcpCredsValidationError == nil {
		return Config{}, errors.New("Multiple IAAS Credentials provided:\n Provide a set of credentials for a single IAAS.")
	}

	if awsCredsValidationError != nil && gcpCredsValidationError != nil {
		return Config{}, fmt.Errorf("Multiple Credential Errors Found: %s\nProvide a full set of credentials for a single IAAS.", gcpCredsValidationError)
	}

	if config.StateFileDir == "" {
		dir, err := ioutil.TempDir("", "")
		if err != nil {
			return Config{}, err
		}
		config.StateFileDir = dir
	}

	return config, nil
}

func validateAWSCreds(config Config) error {
	if config.AWSAccessKeyID == "" {
		return errors.New("aws access key id is missing")
	}

	if config.AWSSecretAccessKey == "" {
		return errors.New("aws secret access key is missing")
	}

	if config.AWSRegion == "" {
		return errors.New("aws region is missing")
	}

	return nil
}

func validateGCPCreds(config Config) error {
	if config.GCPServiceAccountKeyPath == "" {
		return errors.New("gcp service account key path is missing")
	}

	if config.GCPProjectID == "" {
		return errors.New("project id is missing")
	}

	if config.GCPRegion == "" {
		return errors.New("gcp region is missing")
	}

	if config.GCPZone == "" {
		return errors.New("gcp zone is missing")
	}

	return nil
}

func loadConfigFromEnvVars() Config {
	return Config{
		AWSAccessKeyID:           os.Getenv("BBL_AWS_ACCESS_KEY_ID"),
		AWSSecretAccessKey:       os.Getenv("BBL_AWS_SECRET_ACCESS_KEY"),
		AWSRegion:                os.Getenv("BBL_AWS_REGION"),
		GCPServiceAccountKeyPath: os.Getenv("BBL_GCP_SERVICE_ACCOUNT_KEY"),
		GCPProjectID:             os.Getenv("BBL_GCP_PROJECT_ID"),
		GCPRegion:                os.Getenv("BBL_GCP_REGION"),
		GCPZone:                  os.Getenv("BBL_GCP_ZONE"),
		GCPEnvPrefix:             os.Getenv("BBL_GCP_ENV_PREFIX"),
		StateFileDir:             os.Getenv("BBL_STATE_DIR"),
		StemcellPath:             os.Getenv("STEMCELL_PATH"),
		GardenReleasePath:        os.Getenv("GARDEN_RELEASE_PATH"),
		ConcourseReleasePath:     os.Getenv("CONCOURSE_RELEASE_PATH"),
		ConcourseDeploymentPath:  os.Getenv("CONCOURSE_DEPLOYMENT_PATH"),
		EnableTerraformFlag:      os.Getenv("ENABLE_TERRAFORM_FLAG") == "true",
	}
}
<|endoftext|>


<commit_before>// Package metal is the base for a pluggable IRC bot.
package metal

import (
	"crypto/tls"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	irc "github.com/thoj/go-ircevent"

	"github.com/n7st/metal/internal/pkg/util"
)

// Bot contains the IRC bot.
type Bot struct {
	Connection *irc.Connection
	Config     *util.Config
	Logger     *logrus.Logger
	Plugins    []*util.Plugin

	Channels      map[string]bool
	ChannelsMutex *sync.RWMutex
}

// Init sets up an IRC bot connection to the network.
func Init(config *util.Config, logger *logrus.Logger) *Bot {
	connection := irc.IRC(config.IRC.Nickname, config.IRC.Ident)

	connection.Debug = config.IRC.Debug
	connection.VerboseCallbackHandler = config.IRC.Verbose
	connection.RealName = config.IRC.RealName

	if config.IRC.UseTLS {
		connection.UseTLS = true
		connection.TLSConfig = &tls.Config{InsecureSkipVerify: true}
	}

	err := connection.Connect(config.IRC.Hostname)

	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Fatal error connecting to IRC")
	}

	bot := &Bot{Connection: connection, Config: config, Logger: logger, ChannelsMutex: &sync.RWMutex{}}
	bot.Channels = make(map[string]bool)

	plugins, errors := util.LoadPlugins(config.EnabledPlugins())

	for _, p := range plugins {
		// Plugins may run on a ticker a goroutine; start them
		if timer, ok := p.Contrib.(interface{ Timer() }); ok {
			timer.Timer()
		}
	}

	if len(errors) > 0 {
		for _, err := range errors {
			logger.Println(err)
		}
	}

	bot.Plugins = plugins

	for name, fn := range events(bot) {
		bot.Connection.AddCallback(name, fn)
	}

	bot.healthCheck()

	return bot
}

// joinChannels joins either a provided list of channels, or the channels set in
// the bot's configuration.
func (b *Bot) joinChannels(params ...string) {
	var channels []string

	if len(params) > 0 {
		channels = params
	} else {
		channels = b.Config.IRC.Channels
	}

	for _, channel := range channels {
		b.Connection.Join(channel)
	}
}

func (b *Bot) IsOnChannel(channel string) bool {
	b.ChannelsMutex.RLock()

	defer b.ChannelsMutex.RUnlock()

	return b.Channels[channel]
}

// MessageChannels sends one message to many channels.
func (b *Bot) MessageChannels(channels []string, message string) {
	for _, channel := range channels {
		b.MessageChannel(channel, message)

		time.Sleep(1 * time.Second) // Antispam
	}
}

func (b *Bot) MessageChannel(channel string, message string) {
	if b.Config.IRC.Debug {
		b.Logger.Infof("Attempting to message channel %s with message %s", channel, message)
	}

	if b.IsOnChannel(channel) {
		b.Connection.Privmsg(channel, message)
	} else {
		b.Logger.Warnf("Tried to message channel %s with message %s, but I'm not in it", channel, message)
	}
}

// healthCheck checks for a connection to the IRC network and reconnects as
// required.
func (b *Bot) healthCheck() {
	retries := 0

	go func() {
		for {
			select {
			case <-b.Connection.Error:
				b.Logger.Warn("Healthcheck failed")

				if retries > b.Config.IRC.MaxReconnect {
					b.Logger.WithFields(logrus.Fields{
						"retries":     retries,
						"max_retries": b.Config.IRC.MaxReconnect,
					}).Fatal("Maximum reconnection attempts exceeded")
				}

				err := b.Connection.Reconnect()

				if err != nil {
					b.Logger.WithFields(logrus.Fields{
						"error": err.Error(),
					}).Warn("Health check error")
				} else {
					retries = 0
				}
			default:
				if b.Config.IRC.Verbose {
					b.Logger.Debug("Health check successful")
				}
			}

			time.Sleep(b.Config.IRC.ReconnectDelayMinutes * time.Minute)
		}
	}()
}
<commit_msg>#17: Enable TLS certificate check<commit_after>// Package metal is the base for a pluggable IRC bot.
package metal

import (
	"crypto/tls"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	irc "github.com/thoj/go-ircevent"

	"github.com/n7st/metal/internal/pkg/util"
)

// Bot contains the IRC bot.
type Bot struct {
	Connection *irc.Connection
	Config     *util.Config
	Logger     *logrus.Logger
	Plugins    []*util.Plugin

	Channels      map[string]bool
	ChannelsMutex *sync.RWMutex
}

// Init sets up an IRC bot connection to the network.
func Init(config *util.Config, logger *logrus.Logger) *Bot {
	connection := irc.IRC(config.IRC.Nickname, config.IRC.Ident)

	connection.Debug = config.IRC.Debug
	connection.VerboseCallbackHandler = config.IRC.Verbose
	connection.RealName = config.IRC.RealName

	if config.IRC.UseTLS {
		connection.UseTLS = true
		connection.TLSConfig = &tls.Config{}
	}

	err := connection.Connect(config.IRC.Hostname)

	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Fatal error connecting to IRC")
	}

	bot := &Bot{Connection: connection, Config: config, Logger: logger, ChannelsMutex: &sync.RWMutex{}}
	bot.Channels = make(map[string]bool)

	plugins, errors := util.LoadPlugins(config.EnabledPlugins())

	for _, p := range plugins {
		// Plugins may run on a ticker a goroutine; start them
		if timer, ok := p.Contrib.(interface{ Timer() }); ok {
			timer.Timer()
		}
	}

	if len(errors) > 0 {
		for _, err := range errors {
			logger.Println(err)
		}
	}

	bot.Plugins = plugins

	for name, fn := range events(bot) {
		bot.Connection.AddCallback(name, fn)
	}

	bot.healthCheck()

	return bot
}

// joinChannels joins either a provided list of channels, or the channels set in
// the bot's configuration.
func (b *Bot) joinChannels(params ...string) {
	var channels []string

	if len(params) > 0 {
		channels = params
	} else {
		channels = b.Config.IRC.Channels
	}

	for _, channel := range channels {
		b.Connection.Join(channel)
	}
}

func (b *Bot) IsOnChannel(channel string) bool {
	b.ChannelsMutex.RLock()

	defer b.ChannelsMutex.RUnlock()

	return b.Channels[channel]
}

// MessageChannels sends one message to many channels.
func (b *Bot) MessageChannels(channels []string, message string) {
	for _, channel := range channels {
		b.MessageChannel(channel, message)

		time.Sleep(1 * time.Second) // Antispam
	}
}

func (b *Bot) MessageChannel(channel string, message string) {
	if b.Config.IRC.Debug {
		b.Logger.Infof("Attempting to message channel %s with message %s", channel, message)
	}

	if b.IsOnChannel(channel) {
		b.Connection.Privmsg(channel, message)
	} else {
		b.Logger.Warnf("Tried to message channel %s with message %s, but I'm not in it", channel, message)
	}
}

// healthCheck checks for a connection to the IRC network and reconnects as
// required.
func (b *Bot) healthCheck() {
	retries := 0

	go func() {
		for {
			select {
			case <-b.Connection.Error:
				b.Logger.Warn("Healthcheck failed")

				if retries > b.Config.IRC.MaxReconnect {
					b.Logger.WithFields(logrus.Fields{
						"retries":     retries,
						"max_retries": b.Config.IRC.MaxReconnect,
					}).Fatal("Maximum reconnection attempts exceeded")
				}

				err := b.Connection.Reconnect()

				if err != nil {
					b.Logger.WithFields(logrus.Fields{
						"error": err.Error(),
					}).Warn("Health check error")
				} else {
					retries = 0
				}
			default:
				if b.Config.IRC.Verbose {
					b.Logger.Debug("Health check successful")
				}
			}

			time.Sleep(b.Config.IRC.ReconnectDelayMinutes * time.Minute)
		}
	}()
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package audio

import (
	"github.com/gopherjs/gopherjs/js"
)

// Keep this so as not to be destroyed by GC.
var node js.Object
var context js.Object

const bufferSize = 1024
const SampleRate = 44100

type channel struct {
	l             []float32
	r             []float32
	nextInsertion int
}

var channels = make([]*channel, 16)

func init() {
	for i, _ := range channels {
		channels[i] = &channel{
			l: []float32{},
			r: []float32{},
		}
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

var currentBytes = 0

func CurrentBytes() int {
	return currentBytes
}

func Init() {
	context = js.Global.Get("AudioContext").New()
	// TODO: ScriptProcessorNode will be replaced with Audio WebWorker.
	// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
	node = context.Call("createScriptProcessor", bufferSize, 0, 2)
	node.Call("addEventListener", "audioprocess", func(e js.Object) {
		defer func() {
			currentBytes += bufferSize
		}()

		l := e.Get("outputBuffer").Call("getChannelData", 0)
		r := e.Get("outputBuffer").Call("getChannelData", 1)
		inputL := make([]float32, bufferSize)
		inputR := make([]float32, bufferSize)
		for _, ch := range channels {
			if len(ch.l) == 0 {
				continue
			}
			l := min(len(ch.l), bufferSize)
			for i := 0; i < l; i++ {
				inputL[i] += ch.l[i]
				inputR[i] += ch.r[i]
			}
			// TODO: Use copyFromChannel?
			usedLen := min(bufferSize, len(ch.l))
			ch.l = ch.l[usedLen:]
			ch.r = ch.r[usedLen:]
			ch.nextInsertion -= min(bufferSize, ch.nextInsertion)
		}
		for i := 0; i < bufferSize; i++ {
			// TODO: Use copyFromChannel?
			if len(inputL) <= i {
				l.SetIndex(i, 0)
				r.SetIndex(i, 0)
				continue
			}
			l.SetIndex(i, inputL[i])
			r.SetIndex(i, inputR[i])
		}
	})
}

func Update() {
	for _, ch := range channels {
		if len(ch.l) == 0 {
			continue
		}
		ch.nextInsertion += SampleRate / 60
	}
}

func Start() {
	// TODO: For iOS, node should be connected with a buffer node.
	node.Call("connect", context.Get("destination"))
}

func channelAt(i int) *channel {
	if i == -1 {
		for _, ch := range channels {
			if len(ch.l) <= ch.nextInsertion {
				return ch
			}
		}
		return nil
	}
	ch := channels[i]
	if len(ch.l) <= ch.nextInsertion {
		return ch
	}
	return nil
}

func Append(i int, l []float32, r []float32) bool {
	// TODO: Mutex (especially for OpenAL)
	if len(l) != len(r) {
		panic("len(l) must equal to len(r)")
	}
	ch := channelAt(i)
	if ch == nil {
		return false
	}
	print(ch.nextInsertion)
	ch.l = append(ch.l, make([]float32, ch.nextInsertion-len(ch.l))...)
	ch.r = append(ch.r, make([]float32, ch.nextInsertion-len(ch.r))...)
	ch.l = append(ch.l, l...)
	ch.r = append(ch.r, r...)
	return true
}
<commit_msg>Refactoring: only one nextInsertion is needed<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package audio

import (
	"github.com/gopherjs/gopherjs/js"
)

// Keep this so as not to be destroyed by GC.
var node js.Object
var context js.Object

const bufferSize = 1024
const SampleRate = 44100

var nextInsertion = 0

type channel struct {
	l []float32
	r []float32
}

var channels = make([]*channel, 16)

func init() {
	for i, _ := range channels {
		channels[i] = &channel{
			l: []float32{},
			r: []float32{},
		}
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

var currentBytes = 0

func CurrentBytes() int {
	return currentBytes
}

func Init() {
	context = js.Global.Get("AudioContext").New()
	// TODO: ScriptProcessorNode will be replaced with Audio WebWorker.
	// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
	node = context.Call("createScriptProcessor", bufferSize, 0, 2)
	node.Call("addEventListener", "audioprocess", func(e js.Object) {
		defer func() {
			currentBytes += bufferSize
		}()

		l := e.Get("outputBuffer").Call("getChannelData", 0)
		r := e.Get("outputBuffer").Call("getChannelData", 1)
		inputL := make([]float32, bufferSize)
		inputR := make([]float32, bufferSize)
		for _, ch := range channels {
			if len(ch.l) == 0 {
				continue
			}
			l := min(len(ch.l), bufferSize)
			for i := 0; i < l; i++ {
				inputL[i] += ch.l[i]
				inputR[i] += ch.r[i]
			}
			// TODO: Use copyFromChannel?
			usedLen := min(bufferSize, len(ch.l))
			ch.l = ch.l[usedLen:]
			ch.r = ch.r[usedLen:]
		}
		nextInsertion -= min(bufferSize, nextInsertion)
		for i := 0; i < bufferSize; i++ {
			// TODO: Use copyFromChannel?
			if len(inputL) <= i {
				l.SetIndex(i, 0)
				r.SetIndex(i, 0)
				continue
			}
			l.SetIndex(i, inputL[i])
			r.SetIndex(i, inputR[i])
		}
	})
}

func Update() {
	nextInsertion += SampleRate / 60
}

func Start() {
	// TODO: For iOS, node should be connected with a buffer node.
	node.Call("connect", context.Get("destination"))
}

func channelAt(i int) *channel {
	if i == -1 {
		for _, ch := range channels {
			if len(ch.l) <= nextInsertion {
				return ch
			}
		}
		return nil
	}
	ch := channels[i]
	if len(ch.l) <= nextInsertion {
		return ch
	}
	return nil
}

func Append(i int, l []float32, r []float32) bool {
	// TODO: Mutex (especially for OpenAL)
	if len(l) != len(r) {
		panic("len(l) must equal to len(r)")
	}
	ch := channelAt(i)
	if ch == nil {
		return false
	}
	ch.l = append(ch.l, make([]float32, nextInsertion-len(ch.l))...)
	ch.r = append(ch.r, make([]float32, nextInsertion-len(ch.r))...)
	ch.l = append(ch.l, l...)
	ch.r = append(ch.r, r...)
	return true
}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Cloud Development Kit 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
//
//     https://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 main

import (
	"bytes"
	"context"
	"flag"
	"io/ioutil"
	"os"
	"os/exec"
	slashpath "path"
	"path/filepath"
	"strings"
	"text/template"

	"golang.org/x/xerrors"
)

func init_(ctx context.Context, pctx *processContext, args []string) error {
	f := newFlagSet(pctx, "init")
	var modpath string
	var allowExistingDir bool
	f.StringVar(&modpath, "module-path", "", "the module import path for your "+
		"project's go.mod file (required if project is outside of GOPATH)")
	f.StringVar(&modpath, "m", "", "alias for --module-path")
	// TODO(#1918): Remove this flag when empty directories are allowed; it is
	// currently used to enabled tests to create an empty tempdir and then run
	// "init" on it.
	f.BoolVar(&allowExistingDir, "allow-existing-dir", false, "true to allow initializing an existing directory (contents may be overwritten!)")

	if err := f.Parse(args); xerrors.Is(err, flag.ErrHelp) {
		return nil
	} else if err != nil {
		return usagef("gocdk init: %w", err)
	}

	if f.NArg() != 1 {
		return usagef("gocdk init [--module-path=MODULE_IMPORT_PATH] PATH_TO_PROJECT_DIR")
	}

	projectDir := pctx.resolve(f.Arg(0))
	if modpath == "" {
		var err error
		modpath, err = inferModulePath(ctx, pctx, projectDir)
		if err != nil {
			// TODO(clausti): return information about how to mitigate this error
			// e.g. tell them to use --module-path to specify it
			return xerrors.Errorf("gocdk init: %w", err)
		}
	}

	// TODO(#1918): allow an existing empty directory, for some definition of empty.
	if _, err := os.Stat(projectDir); err == nil {
		if !allowExistingDir {
			return xerrors.Errorf("gocdk init: %s already exists", projectDir)
		}
	} else if !os.IsNotExist(err) {
		return xerrors.Errorf("gocdk init: %w", err)
	}

	tmplValues := struct {
		ProjectName string
		ModulePath  string
	}{
		ProjectName: filepath.Base(projectDir),
		ModulePath:  modpath,
	}
	if err := materializeTemplateDir(projectDir, "init", tmplValues); err != nil {
		return xerrors.Errorf("gocdk init: %w", err)
	}
	return nil
}

func materializeTemplateDir(dst string, srcRoot string, data interface{}) error {
	dir, err := static.Open(srcRoot)
	if err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	infos, err := dir.Readdir(-1)
	dir.Close()
	if err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	if err := os.MkdirAll(dst, 0777); err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	for _, info := range infos {
		name := info.Name()
		currDst := filepath.Join(dst, name)
		currSrc := slashpath.Join(srcRoot, name)
		if info.IsDir() {
			if err := materializeTemplateDir(currDst, currSrc, data); err != nil {
				return err
			}
			continue
		}
		f, err := static.Open(currSrc)
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		templateSource, err := ioutil.ReadAll(f)
		f.Close()
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		tmpl, err := template.New(name).Parse(string(templateSource))
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		buf := new(bytes.Buffer)
		if err := tmpl.Execute(buf, data); err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		if err := ioutil.WriteFile(currDst, buf.Bytes(), 0666); err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
	}
	return nil
}

// inferModulePath will check the default GOPATH to attempt to infer the module
// import path for the project.
func inferModulePath(ctx context.Context, pctx *processContext, projectDir string) (string, error) {
	// TODO(issue #2016): Add tests for init behavior when module-path is not given.
	cmd := exec.CommandContext(ctx, "go", "env", "GOPATH")
	cmd.Dir = pctx.workdir
	cmd.Env = pctx.env
	gopath, err := cmd.Output()
	if err != nil {
		return "", xerrors.Errorf("infer module path: %w", err)
	}
	// Check if the projectDir is relative to GOPATH.
	rel, err := filepath.Rel(strings.TrimSuffix(string(gopath), "\n"), projectDir)
	if err != nil {
		return "", xerrors.Errorf("infer module path: %w", err)
	}
	inGOPATH := !strings.HasPrefix(rel, ".."+string(filepath.Separator))
	if !inGOPATH {
		// If the project dir is outside of GOPATH, we can't infer the module import path.
		return "", xerrors.Errorf("infer module path: %s not in GOPATH", projectDir)
	}
	return filepath.ToSlash(rel), nil
}
<commit_msg>internal/cmd/gocdk: add success text to init (#2125)<commit_after>// Copyright 2019 The Go Cloud Development Kit 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
//
//     https://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 main

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	slashpath "path"
	"path/filepath"
	"strings"
	"text/template"

	"golang.org/x/xerrors"
)

func init_(ctx context.Context, pctx *processContext, args []string) error {
	f := newFlagSet(pctx, "init")
	var modpath string
	var allowExistingDir bool
	f.StringVar(&modpath, "module-path", "", "the module import path for your "+
		"project's go.mod file (required if project is outside of GOPATH)")
	f.StringVar(&modpath, "m", "", "alias for --module-path")
	// TODO(#1918): Remove this flag when empty directories are allowed; it is
	// currently used to enabled tests to create an empty tempdir and then run
	// "init" on it.
	f.BoolVar(&allowExistingDir, "allow-existing-dir", false, "true to allow initializing an existing directory (contents may be overwritten!)")

	if err := f.Parse(args); xerrors.Is(err, flag.ErrHelp) {
		return nil
	} else if err != nil {
		return usagef("gocdk init: %w", err)
	}

	if f.NArg() != 1 {
		return usagef("gocdk init [--module-path=MODULE_IMPORT_PATH] PATH_TO_PROJECT_DIR")
	}

	projectDir := pctx.resolve(f.Arg(0))
	if modpath == "" {
		var err error
		modpath, err = inferModulePath(ctx, pctx, projectDir)
		if err != nil {
			// TODO(clausti): return information about how to mitigate this error
			// e.g. tell them to use --module-path to specify it
			return xerrors.Errorf("gocdk init: %w", err)
		}
	}

	// TODO(#1918): allow an existing empty directory, for some definition of empty.
	if _, err := os.Stat(projectDir); err == nil {
		if !allowExistingDir {
			return xerrors.Errorf("gocdk init: %s already exists", projectDir)
		}
	} else if !os.IsNotExist(err) {
		return xerrors.Errorf("gocdk init: %w", err)
	}

	tmplValues := struct {
		ProjectName string
		ModulePath  string
	}{
		ProjectName: filepath.Base(projectDir),
		ModulePath:  modpath,
	}
	if err := materializeTemplateDir(projectDir, "init", tmplValues); err != nil {
		return xerrors.Errorf("gocdk init: %w", err)
	}
	fmt.Fprintf(pctx.stdout, "Project created at %s with:\n", projectDir)
	fmt.Fprintln(pctx.stdout, "- Go HTTP server")
	fmt.Fprintln(pctx.stdout, "- Dockerfile")
	fmt.Fprintln(pctx.stdout, "- 'dev' biome for local development settings")
	fmt.Fprintln(pctx.stdout)
	fmt.Fprintf(pctx.stdout, "Run `cd %s`, then run:\n", f.Arg(0))
	fmt.Fprintln(pctx.stdout, "- `gocdk serve` to run the server locally with live code reloading")
	fmt.Fprintln(pctx.stdout, "- `gocdk demo` to test new APIs")
	fmt.Fprintln(pctx.stdout, "- `gocdk build` to build a Docker container")
	fmt.Fprintln(pctx.stdout, "- `gocdk biome add` to configure launch settings")
	return nil
}

func materializeTemplateDir(dst string, srcRoot string, data interface{}) error {
	dir, err := static.Open(srcRoot)
	if err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	infos, err := dir.Readdir(-1)
	dir.Close()
	if err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	if err := os.MkdirAll(dst, 0777); err != nil {
		return xerrors.Errorf("materialize %s at %s: %w", srcRoot, dst, err)
	}
	for _, info := range infos {
		name := info.Name()
		currDst := filepath.Join(dst, name)
		currSrc := slashpath.Join(srcRoot, name)
		if info.IsDir() {
			if err := materializeTemplateDir(currDst, currSrc, data); err != nil {
				return err
			}
			continue
		}
		f, err := static.Open(currSrc)
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		templateSource, err := ioutil.ReadAll(f)
		f.Close()
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		tmpl, err := template.New(name).Parse(string(templateSource))
		if err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		buf := new(bytes.Buffer)
		if err := tmpl.Execute(buf, data); err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
		if err := ioutil.WriteFile(currDst, buf.Bytes(), 0666); err != nil {
			return xerrors.Errorf("materialize %s at %s: %w", currSrc, currDst, err)
		}
	}
	return nil
}

// inferModulePath will check the default GOPATH to attempt to infer the module
// import path for the project.
func inferModulePath(ctx context.Context, pctx *processContext, projectDir string) (string, error) {
	// TODO(issue #2016): Add tests for init behavior when module-path is not given.
	cmd := exec.CommandContext(ctx, "go", "env", "GOPATH")
	cmd.Dir = pctx.workdir
	cmd.Env = pctx.env
	gopath, err := cmd.Output()
	if err != nil {
		return "", xerrors.Errorf("infer module path: %w", err)
	}
	// Check if the projectDir is relative to GOPATH.
	rel, err := filepath.Rel(strings.TrimSuffix(string(gopath), "\n"), projectDir)
	if err != nil {
		return "", xerrors.Errorf("infer module path: %w", err)
	}
	inGOPATH := !strings.HasPrefix(rel, ".."+string(filepath.Separator))
	if !inGOPATH {
		// If the project dir is outside of GOPATH, we can't infer the module import path.
		return "", xerrors.Errorf("infer module path: %s not in GOPATH", projectDir)
	}
	return filepath.ToSlash(rel), nil
}
<|endoftext|>


<commit_before>package config

import (
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
)

const (
	RTColorscheme = 0
	RTSyntax      = 1
	RTHelp        = 2
	RTPlugin      = 3
	NumTypes      = 4 // How many filetypes are there
)

type RTFiletype byte

// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
	// Name returns a name of the file without paths or extensions
	Name() string
	// Data returns the content of the file.
	Data() ([]byte, error)
}

// allFiles contains all available files, mapped by filetype
var allFiles [NumTypes][]RuntimeFile

// some file on filesystem
type realFile string

// some asset file
type assetFile string

// some file on filesystem but with a different name
type namedFile struct {
	realFile
	name string
}

// a file with the data stored in memory
type memoryFile struct {
	name string
	data []byte
}

func (mf memoryFile) Name() string {
	return mf.name
}
func (mf memoryFile) Data() ([]byte, error) {
	return mf.data, nil
}

func (rf realFile) Name() string {
	fn := filepath.Base(string(rf))
	return fn[:len(fn)-len(filepath.Ext(fn))]
}

func (rf realFile) Data() ([]byte, error) {
	return ioutil.ReadFile(string(rf))
}

func (af assetFile) Name() string {
	fn := path.Base(string(af))
	return fn[:len(fn)-len(path.Ext(fn))]
}

func (af assetFile) Data() ([]byte, error) {
	return Asset(string(af))
}

func (nf namedFile) Name() string {
	return nf.name
}

// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
}

// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
	files, _ := ioutil.ReadDir(directory)
	for _, f := range files {
		if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
			fullPath := filepath.Join(directory, f.Name())
			AddRuntimeFile(fileType, realFile(fullPath))
		}
	}
}

// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
	files, err := AssetDir(directory)
	if err != nil {
		return
	}
	for _, f := range files {
		if ok, _ := path.Match(pattern, f); ok {
			AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
		}
	}
}

// FindRuntimeFile finds a runtime file of the given filetype and name
// will return nil if no file was found
func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
	for _, f := range ListRuntimeFiles(fileType) {
		if f.Name() == name {
			return f
		}
	}
	return nil
}

// ListRuntimeFiles lists all known runtime files for the given filetype
func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return allFiles[fileType]
}

// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
	add := func(fileType RTFiletype, dir, pattern string) {
		AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
		AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
	}

	add(RTColorscheme, "colorschemes", "*.micro")
	add(RTSyntax, "syntax", "*.yaml")
	add(RTHelp, "help", "*.md")

	initlua := filepath.Join(ConfigDir, "init.lua")
	if _, err := os.Stat(initlua); !os.IsNotExist(err) {
		p := new(Plugin)
		p.Name = "initlua"
		p.Srcs = append(p.Srcs, realFile(initlua))
		Plugins = append(Plugins, p)
	}

	// Search ConfigDir for plugin-scripts
	plugdir := filepath.Join(ConfigDir, "plugins")
	files, _ := ioutil.ReadDir(plugdir)
	for _, d := range files {
		if d.IsDir() {
			srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
			p := new(Plugin)
			p.Name = d.Name()
			for _, f := range srcs {
				if strings.HasSuffix(f.Name(), ".lua") {
					p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
				} else if f.Name() == "info.json" {
					data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
					if err != nil {
						continue
					}
					p.Info, _ = NewPluginInfo(data)
				}
			}
			Plugins = append(Plugins, p)
		}
	}

	plugdir = filepath.Join("runtime", "plugins")
	if files, err := AssetDir(plugdir); err == nil {
		for _, d := range files {
			if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
				p := new(Plugin)
				p.Name = d
				p.Default = true
				for _, f := range srcs {
					if strings.HasSuffix(f, ".lua") {
						p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
					} else if f == "info.json" {
						data, err := Asset(filepath.Join(plugdir, d, "info.json"))
						if err != nil {
							continue
						}
						p.Info, _ = NewPluginInfo(data)
					}
				}
				Plugins = append(Plugins, p)
			}
		}
	}
}

// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
	if file := FindRuntimeFile(fileType, name); file != nil {
		if data, err := file.Data(); err == nil {
			return string(data)
		}
	}
	return ""
}

// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
func PluginListRuntimeFiles(fileType RTFiletype) []string {
	files := ListRuntimeFiles(fileType)
	result := make([]string, len(files))
	for i, f := range files {
		result[i] = f.Name()
	}
	return result
}

// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
	fullpath := filepath.Join(ConfigDir, "plugins", plugin, filePath)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFile(filetype, realFile(fullpath))
	} else {
		fullpath = path.Join("runtime", "plugins", plugin, filePath)
		AddRuntimeFile(filetype, assetFile(fullpath))
	}
}

// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
	fullpath := filepath.Join(ConfigDir, "plugins", plugin, directory)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
	} else {
		fullpath = path.Join("runtime", "plugins", plugin, directory)
		AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
	}
}

// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
	AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}
<commit_msg>Use plugin name defined in info and require it to be an identifier<commit_after>package config

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	RTColorscheme = 0
	RTSyntax      = 1
	RTHelp        = 2
	RTPlugin      = 3
	NumTypes      = 4 // How many filetypes are there
)

type RTFiletype byte

// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
	// Name returns a name of the file without paths or extensions
	Name() string
	// Data returns the content of the file.
	Data() ([]byte, error)
}

// allFiles contains all available files, mapped by filetype
var allFiles [NumTypes][]RuntimeFile

// some file on filesystem
type realFile string

// some asset file
type assetFile string

// some file on filesystem but with a different name
type namedFile struct {
	realFile
	name string
}

// a file with the data stored in memory
type memoryFile struct {
	name string
	data []byte
}

func (mf memoryFile) Name() string {
	return mf.name
}
func (mf memoryFile) Data() ([]byte, error) {
	return mf.data, nil
}

func (rf realFile) Name() string {
	fn := filepath.Base(string(rf))
	return fn[:len(fn)-len(filepath.Ext(fn))]
}

func (rf realFile) Data() ([]byte, error) {
	return ioutil.ReadFile(string(rf))
}

func (af assetFile) Name() string {
	fn := path.Base(string(af))
	return fn[:len(fn)-len(path.Ext(fn))]
}

func (af assetFile) Data() ([]byte, error) {
	return Asset(string(af))
}

func (nf namedFile) Name() string {
	return nf.name
}

// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
	allFiles[fileType] = append(allFiles[fileType], file)
}

// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
	files, _ := ioutil.ReadDir(directory)
	for _, f := range files {
		if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
			fullPath := filepath.Join(directory, f.Name())
			AddRuntimeFile(fileType, realFile(fullPath))
		}
	}
}

// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
	files, err := AssetDir(directory)
	if err != nil {
		return
	}
	for _, f := range files {
		if ok, _ := path.Match(pattern, f); ok {
			AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
		}
	}
}

// FindRuntimeFile finds a runtime file of the given filetype and name
// will return nil if no file was found
func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
	for _, f := range ListRuntimeFiles(fileType) {
		if f.Name() == name {
			return f
		}
	}
	return nil
}

// ListRuntimeFiles lists all known runtime files for the given filetype
func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
	return allFiles[fileType]
}

// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
	add := func(fileType RTFiletype, dir, pattern string) {
		AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
		AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
	}

	add(RTColorscheme, "colorschemes", "*.micro")
	add(RTSyntax, "syntax", "*.yaml")
	add(RTHelp, "help", "*.md")

	initlua := filepath.Join(ConfigDir, "init.lua")
	if _, err := os.Stat(initlua); !os.IsNotExist(err) {
		p := new(Plugin)
		p.Name = "initlua"
		p.Srcs = append(p.Srcs, realFile(initlua))
		Plugins = append(Plugins, p)
	}

	// Search ConfigDir for plugin-scripts
	plugdir := filepath.Join(ConfigDir, "plug")
	files, _ := ioutil.ReadDir(plugdir)

	isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString

	for _, d := range files {
		if d.IsDir() {
			srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
			p := new(Plugin)
			p.Name = d.Name()
			for _, f := range srcs {
				if strings.HasSuffix(f.Name(), ".lua") {
					p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
				} else if f.Name() == "info.json" {
					data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
					if err != nil {
						continue
					}
					p.Info, _ = NewPluginInfo(data)
					p.Name = p.Info.Name
				}
			}

			if !isID(p.Name) {
				log.Println("Invalid plugin name", p.Name)
				continue
			}
			Plugins = append(Plugins, p)
		}
	}

	plugdir = filepath.Join("runtime", "plugins")
	if files, err := AssetDir(plugdir); err == nil {
		for _, d := range files {
			if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
				p := new(Plugin)
				p.Name = d
				p.Default = true
				for _, f := range srcs {
					if strings.HasSuffix(f, ".lua") {
						p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
					} else if f == "info.json" {
						data, err := Asset(filepath.Join(plugdir, d, "info.json"))
						if err != nil {
							continue
						}
						p.Info, _ = NewPluginInfo(data)
						p.Name = p.Info.Name
					}
				}
				if !isID(p.Name) {
					log.Println("Invalid plugin name", p.Name)
					continue
				}
				Plugins = append(Plugins, p)
			}
		}
	}
}

// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
	if file := FindRuntimeFile(fileType, name); file != nil {
		if data, err := file.Data(); err == nil {
			return string(data)
		}
	}
	return ""
}

// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
func PluginListRuntimeFiles(fileType RTFiletype) []string {
	files := ListRuntimeFiles(fileType)
	result := make([]string, len(files))
	for i, f := range files {
		result[i] = f.Name()
	}
	return result
}

// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
	fullpath := filepath.Join(ConfigDir, "plug", plugin, filePath)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFile(filetype, realFile(fullpath))
	} else {
		fullpath = path.Join("runtime", "plugins", plugin, filePath)
		AddRuntimeFile(filetype, assetFile(fullpath))
	}
}

// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
	fullpath := filepath.Join(ConfigDir, "plug", plugin, directory)
	if _, err := os.Stat(fullpath); err == nil {
		AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
	} else {
		fullpath = path.Join("runtime", "plugins", plugin, directory)
		AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
	}
}

// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
	AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}
<|endoftext|>


<commit_before>package uplink

import (
	"bytes"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"sort"
	"time"

	"github.com/brocaar/loraserver/api/gw"
	"github.com/brocaar/loraserver/internal/common"
	"github.com/brocaar/loraserver/internal/models"
	"github.com/brocaar/lorawan"
	"github.com/garyburd/redigo/redis"
)

// Templates used for generating Redis keys
const (
	CollectKeyTempl     = "loraserver:rx:collect:%s"
	CollectLockKeyTempl = "loraserver:rx:collect:%s:lock"
)

// collectAndCallOnce collects the package, sleeps the configured duraction and
// calls the callback only once with a slice of packets, sorted by signal
// strength (strongest at index 0). This method exists since multiple gateways
// are able to receive the same packet, but the packet needs to processed
// only once.
// It is safe to collect the same packet received by the same gateway twice.
// Since the underlying storage type is a set, the result will always be a
// unique set per gateway MAC and packet MIC.
func collectAndCallOnce(p *redis.Pool, rxPacket gw.RXPacket, callback func(packet models.RXPacket) error) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(rxPacket); err != nil {
		return fmt.Errorf("encode rx packet error: %s", err)
	}
	c := p.Get()
	defer c.Close()

	// store the packet in a set with DeduplicationDelay expiration
	// in case the packet is received by multiple gateways, the set will contain
	// each packet.
	// since we can't trust the MIC (in case of a join-request, it will be
	// validated after the collect), we generate a new one and use it as the
	// hash for the storage key.
	if err := rxPacket.PHYPayload.SetMIC(lorawan.AES128Key{}); err != nil {
		return fmt.Errorf("set mic error: %s", err)
	}

	mic := hex.EncodeToString(rxPacket.PHYPayload.MIC[:])
	key := fmt.Sprintf(CollectKeyTempl, mic)
	lockKey := fmt.Sprintf(CollectLockKeyTempl, mic)

	// this way we can set a really low DeduplicationDelay for testing, without
	// the risk that the set already expired in redis on read
	deduplicationTTL := common.DeduplicationDelay * 2
	if deduplicationTTL < time.Millisecond*200 {
		deduplicationTTL = time.Millisecond * 200
	}

	c.Send("MULTI")
	c.Send("SADD", key, buf.Bytes())
	c.Send("PEXPIRE", key, int64(deduplicationTTL)/int64(time.Millisecond))
	_, err := c.Do("EXEC")
	if err != nil {
		return fmt.Errorf("add rx packet to collect set error: %s", err)
	}

	// acquire a lock on processing this packet
	_, err = redis.String((c.Do("SET", lockKey, "lock", "PX", int64(deduplicationTTL)/int64(time.Millisecond), "NX")))
	if err != nil {
		if err == redis.ErrNil {
			// the packet processing is already locked by an other process
			// so there is nothing to do anymore :-)
			return nil
		}
		return fmt.Errorf("acquire lock error: %s", err)
	}

	// wait the configured amount of time, more packets might be received
	// from other gateways
	time.Sleep(common.DeduplicationDelay)

	// collect all packets from the set
	var rxPacketWithRXInfoSet models.RXPacket
	payloads, err := redis.ByteSlices(c.Do("SMEMBERS", key))
	if err != nil {
		return fmt.Errorf("get collect set members error: %s", err)
	}
	if len(payloads) == 0 {
		return errors.New("zero items in collect set")
	}

	for i, b := range payloads {
		var packet gw.RXPacket
		if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&packet); err != nil {
			return fmt.Errorf("decode rx packet error: %s", err)
		}

		if i == 0 {
			rxPacketWithRXInfoSet.PHYPayload = packet.PHYPayload
		}
		rxPacketWithRXInfoSet.RXInfoSet = append(rxPacketWithRXInfoSet.RXInfoSet, packet.RXInfo)
	}

	sort.Sort(rxPacketWithRXInfoSet.RXInfoSet)
	return callback(rxPacketWithRXInfoSet)
}
<commit_msg>Use full PHYPayload as deduplication key.<commit_after>package uplink

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"sort"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/pkg/errors"

	"github.com/brocaar/loraserver/api/gw"
	"github.com/brocaar/loraserver/internal/common"
	"github.com/brocaar/loraserver/internal/models"
)

// Templates used for generating Redis keys
const (
	CollectKeyTempl     = "loraserver:rx:collect:%s"
	CollectLockKeyTempl = "loraserver:rx:collect:%s:lock"
)

// collectAndCallOnce collects the package, sleeps the configured duraction and
// calls the callback only once with a slice of packets, sorted by signal
// strength (strongest at index 0). This method exists since multiple gateways
// are able to receive the same packet, but the packet needs to processed
// only once.
// It is safe to collect the same packet received by the same gateway twice.
// Since the underlying storage type is a set, the result will always be a
// unique set per gateway MAC and packet MIC.
func collectAndCallOnce(p *redis.Pool, rxPacket gw.RXPacket, callback func(packet models.RXPacket) error) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(rxPacket); err != nil {
		return fmt.Errorf("encode rx packet error: %s", err)
	}
	c := p.Get()
	defer c.Close()

	// store the packet in a set with DeduplicationDelay expiration
	// in case the packet is received by multiple gateways, the set will contain
	// each packet.
	// The text representation of the PHYPayload is used as key.
	phyB, err := rxPacket.PHYPayload.MarshalText()
	if err != nil {
		return errors.Wrap(err, "marshal to text error")
	}

	key := fmt.Sprintf(CollectKeyTempl, string(phyB))
	lockKey := fmt.Sprintf(CollectLockKeyTempl, string(phyB))

	// this way we can set a really low DeduplicationDelay for testing, without
	// the risk that the set already expired in redis on read
	deduplicationTTL := common.DeduplicationDelay * 2
	if deduplicationTTL < time.Millisecond*200 {
		deduplicationTTL = time.Millisecond * 200
	}

	c.Send("MULTI")
	c.Send("SADD", key, buf.Bytes())
	c.Send("PEXPIRE", key, int64(deduplicationTTL)/int64(time.Millisecond))
	_, err = c.Do("EXEC")
	if err != nil {
		return fmt.Errorf("add rx packet to collect set error: %s", err)
	}

	// acquire a lock on processing this packet
	_, err = redis.String((c.Do("SET", lockKey, "lock", "PX", int64(deduplicationTTL)/int64(time.Millisecond), "NX")))
	if err != nil {
		if err == redis.ErrNil {
			// the packet processing is already locked by an other process
			// so there is nothing to do anymore :-)
			return nil
		}
		return fmt.Errorf("acquire lock error: %s", err)
	}

	// wait the configured amount of time, more packets might be received
	// from other gateways
	time.Sleep(common.DeduplicationDelay)

	// collect all packets from the set
	var rxPacketWithRXInfoSet models.RXPacket
	payloads, err := redis.ByteSlices(c.Do("SMEMBERS", key))
	if err != nil {
		return fmt.Errorf("get collect set members error: %s", err)
	}
	if len(payloads) == 0 {
		return errors.New("zero items in collect set")
	}

	for i, b := range payloads {
		var packet gw.RXPacket
		if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&packet); err != nil {
			return fmt.Errorf("decode rx packet error: %s", err)
		}

		if i == 0 {
			rxPacketWithRXInfoSet.PHYPayload = packet.PHYPayload
		}
		rxPacketWithRXInfoSet.RXInfoSet = append(rxPacketWithRXInfoSet.RXInfoSet, packet.RXInfo)
	}

	sort.Sort(rxPacketWithRXInfoSet.RXInfoSet)
	return callback(rxPacketWithRXInfoSet)
}
<|endoftext|>


<commit_before>package adhier

import (
	"fmt"
	"testing"

	"github.com/ready-steady/numan/basis/linhat"
	"github.com/ready-steady/numan/grid/newcot"
	"github.com/ready-steady/support/assert"
)

func TestConstructStep(t *testing.T) {
	fixtureStep.prepare()
	algorithm := makeInterpolator(1, 1, fixtureStep.surrogate.level)

	surrogate := algorithm.Compute(step)

	assert.Equal(surrogate, fixtureStep.surrogate, t)
}

func TestEvaluateStep(t *testing.T) {
	fixtureStep.prepare()
	algorithm := makeInterpolator(1, 1, 0)

	values := algorithm.Evaluate(fixtureStep.surrogate, fixtureStep.points)

	assert.Equal(values, fixtureStep.values, t)
}

func TestConstructCube(t *testing.T) {
	fixtureCube.prepare()
	algorithm := makeInterpolator(2, 1, fixtureCube.surrogate.level)

	surrogate := algorithm.Compute(cube)

	assert.Equal(surrogate, fixtureCube.surrogate, t)
}

func TestConstructBox(t *testing.T) {
	fixtureBox.prepare()
	algorithm := makeInterpolator(2, 3, fixtureBox.surrogate.level)

	surrogate := algorithm.Compute(box)

	assert.Equal(surrogate, fixtureBox.surrogate, t)
}

func TestEvaluateBox(t *testing.T) {
	fixtureBox.prepare()
	algorithm := makeInterpolator(2, 3, 0)

	values := algorithm.Evaluate(fixtureBox.surrogate, fixtureBox.points)

	assert.AlmostEqual(values, fixtureBox.values, t)
}

func BenchmarkHat(b *testing.B) {
	algorithm := makeInterpolator(1, 1, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(hat)
	}
}

func BenchmarkCube(b *testing.B) {
	algorithm := makeInterpolator(2, 1, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(cube)
	}
}

func BenchmarkBox(b *testing.B) {
	algorithm := makeInterpolator(2, 3, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(box)
	}
}

func BenchmarkMany(b *testing.B) {
	algorithm := makeInterpolator(2, 1000, 0)
	function := many(2, 1000)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(function)
	}
}

// A one-input-one-output scenario with a non-smooth function.
func ExampleSelf_step() {
	const (
		inputs  = 1
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 19
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(step)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 1, outputs: 1, levels: 19, nodes: 38}
}

// A one-input-one-output scenario with a smooth function.
func ExampleSelf_hat() {
	const (
		inputs  = 1
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 9
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(hat)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 1, outputs: 1, levels: 9, nodes: 305}
}

// A multiple-input-one-output scenario with a non-smooth function.
func ExampleSelf_cube() {
	const (
		inputs  = 2
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 9
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(cube)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 2, outputs: 1, levels: 9, nodes: 377}
}

// A multiple-input-many-output scenario with a non-smooth function.
func ExampleSelf_many() {
	const (
		inputs  = 2
		outputs = 1000
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	algorithm := New(grid, basis, DefaultConfig, outputs)

	surrogate := algorithm.Compute(many(inputs, outputs))
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 2, outputs: 1000, levels: 9, nodes: 362}
}

func makeInterpolator(ic, oc uint16, ml uint8) *Interpolator {
	config := DefaultConfig

	if ml > 0 {
		config.MaxLevel = ml
	}

	return New(newcot.NewClosed(ic), linhat.NewClosed(ic), config, oc)
}
<commit_msg>Renamed a couple of examples<commit_after>package adhier

import (
	"fmt"
	"testing"

	"github.com/ready-steady/numan/basis/linhat"
	"github.com/ready-steady/numan/grid/newcot"
	"github.com/ready-steady/support/assert"
)

func TestConstructStep(t *testing.T) {
	fixtureStep.prepare()
	algorithm := makeInterpolator(1, 1, fixtureStep.surrogate.level)

	surrogate := algorithm.Compute(step)

	assert.Equal(surrogate, fixtureStep.surrogate, t)
}

func TestEvaluateStep(t *testing.T) {
	fixtureStep.prepare()
	algorithm := makeInterpolator(1, 1, 0)

	values := algorithm.Evaluate(fixtureStep.surrogate, fixtureStep.points)

	assert.Equal(values, fixtureStep.values, t)
}

func TestConstructCube(t *testing.T) {
	fixtureCube.prepare()
	algorithm := makeInterpolator(2, 1, fixtureCube.surrogate.level)

	surrogate := algorithm.Compute(cube)

	assert.Equal(surrogate, fixtureCube.surrogate, t)
}

func TestConstructBox(t *testing.T) {
	fixtureBox.prepare()
	algorithm := makeInterpolator(2, 3, fixtureBox.surrogate.level)

	surrogate := algorithm.Compute(box)

	assert.Equal(surrogate, fixtureBox.surrogate, t)
}

func TestEvaluateBox(t *testing.T) {
	fixtureBox.prepare()
	algorithm := makeInterpolator(2, 3, 0)

	values := algorithm.Evaluate(fixtureBox.surrogate, fixtureBox.points)

	assert.AlmostEqual(values, fixtureBox.values, t)
}

func BenchmarkHat(b *testing.B) {
	algorithm := makeInterpolator(1, 1, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(hat)
	}
}

func BenchmarkCube(b *testing.B) {
	algorithm := makeInterpolator(2, 1, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(cube)
	}
}

func BenchmarkBox(b *testing.B) {
	algorithm := makeInterpolator(2, 3, 0)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(box)
	}
}

func BenchmarkMany(b *testing.B) {
	algorithm := makeInterpolator(2, 1000, 0)
	function := many(2, 1000)

	for i := 0; i < b.N; i++ {
		_ = algorithm.Compute(function)
	}
}

// A one-input-one-output scenario with a non-smooth function.
func ExampleInterpolator_step() {
	const (
		inputs  = 1
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 19
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(step)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 1, outputs: 1, levels: 19, nodes: 38}
}

// A one-input-one-output scenario with a smooth function.
func ExampleInterpolator_hat() {
	const (
		inputs  = 1
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 9
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(hat)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 1, outputs: 1, levels: 9, nodes: 305}
}

// A multiple-input-one-output scenario with a non-smooth function.
func ExampleInterpolator_cube() {
	const (
		inputs  = 2
		outputs = 1
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	config := DefaultConfig
	config.MaxLevel = 9
	algorithm := New(grid, basis, config, outputs)

	surrogate := algorithm.Compute(cube)
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 2, outputs: 1, levels: 9, nodes: 377}
}

// A multiple-input-many-output scenario with a non-smooth function.
func ExampleInterpolator_many() {
	const (
		inputs  = 2
		outputs = 1000
	)

	grid := newcot.NewClosed(inputs)
	basis := linhat.NewClosed(inputs)

	algorithm := New(grid, basis, DefaultConfig, outputs)

	surrogate := algorithm.Compute(many(inputs, outputs))
	fmt.Println(surrogate)

	// Output:
	// Surrogate{inputs: 2, outputs: 1000, levels: 9, nodes: 362}
}

func makeInterpolator(ic, oc uint16, ml uint8) *Interpolator {
	config := DefaultConfig

	if ml > 0 {
		config.MaxLevel = ml
	}

	return New(newcot.NewClosed(ic), linhat.NewClosed(ic), config, oc)
}
<|endoftext|>


<commit_before>package main

import (
	"log"

	"github.com/jellevandenhooff/keytree/crypto"
	"github.com/jellevandenhooff/keytree/mirror"
	"github.com/jellevandenhooff/keytree/trie"
	"github.com/jellevandenhooff/keytree/wire"
	"golang.org/x/net/context"
)

type tracker struct {
	ctx context.Context

	mirror *mirror.Mirror

	conn               *wire.KeyTreeClient
	server             *Server
	address, publicKey string

	// resolve queue
	queue chan crypto.Hash
}

func (t *tracker) FullSync(s *wire.SignedRoot, n *trie.Node) {
	t.server.considerTrie(t.publicKey, n, s)

	t.server.mu.Lock()
	localRoot := t.server.localTrie.root
	t.server.mu.Unlock()

	_ = t.reconcile(localRoot, n, 0)
}

func (t *tracker) PartialSync(s *wire.SignedRoot, n *trie.Node) {
	t.server.considerTrie(t.publicKey, n, s)

	t.server.mu.Lock()
	localRoot := t.server.localTrie.root
	t.server.mu.Unlock()

	_ = t.reconcile(localRoot, n, 0)
}

func (t *tracker) Updated(s *wire.SignedRoot, n *trie.Node, u []*wire.TrieLeaf) {
	t.server.considerTrie(t.publicKey, n, s)

	for _, leaf := range u {
		t.queue <- leaf.NameHash
	}
}

func (t *tracker) fixer() error {
	for t.ctx.Err() == nil {
		select {
		case h := <-t.queue:
			t.fixup(h)
		case <-t.ctx.Done():
		}
	}
	return t.ctx.Err()
}

func (t *tracker) fixup(h crypto.Hash) {
	t.server.reconcileLocks.Lock(h)
	defer t.server.reconcileLocks.Unlock(h)

	localEntry, err := t.server.db.Read(h)
	if err != nil {
		log.Println(err)
		return
	}

	var since uint64
	if localEntry != nil {
		since = localEntry.Entry.Timestamp + 1
	}

	for {
		update, err := t.conn.History(h, since)
		if err == wire.ErrNotFound {
			break
		} else if err != nil {
			log.Println(err)
			return
		}

		// Try applying all updates in order. If it doesn't work, keep trying
		// anyway!
		if err := t.server.doUpdate(update); err != nil {
			log.Println(err)
		}
		since = update.Entry.Timestamp + 1
	}
}

func areSameEntry(local, remote *trie.Node) bool {
	if local == nil && remote.Entry != nil {
		return true
	}

	if remote == nil && local.Entry != nil {
		return true
	}

	return local != nil && local.Entry != nil &&
		remote != nil && remote.Entry != nil &&
		local.Entry.NameHash == remote.Entry.NameHash
}

func (t *tracker) reconcile(local, remote *trie.Node, depth int) error {
	if err := t.ctx.Err(); err != nil {
		return err
	}

	if remote == nil || local.Hash() == remote.Hash() {
		return nil
	}

	if areSameEntry(local, remote) {
		select {
		case t.queue <- remote.Entry.NameHash:
		case <-t.ctx.Done():
		}
	} else {
		localChildren := local.Split(depth)
		remoteChildren := remote.Split(depth)
		for i := 0; i < 2; i++ {
			if err := t.reconcile(localChildren[i], remoteChildren[i], depth+1); err != nil {
				return err
			}
		}
	}
	return nil
}

func runTracker(ctx context.Context, s *Server, address string, publicKey string) *tracker {
	log.Printf("spawning tracker for %s at %s", publicKey, address)

	conn := wire.NewKeyTreeClient("http://" + address)

	t := &tracker{
		ctx:       ctx,
		conn:      conn,
		server:    s,
		address:   address,
		publicKey: publicKey,
		queue:     make(chan crypto.Hash, reconcileQueueSize),
	}

	t.mirror = mirror.NewMirror(ctx, s.coordinator, conn, address, publicKey, nil, t)

	for i := 0; i < fixerParallelism; i++ {
		go t.fixer()
	}
	go t.mirror.Run()

	return t
}
<commit_msg>add insight as todo<commit_after>package main

import (
	"log"

	"github.com/jellevandenhooff/keytree/crypto"
	"github.com/jellevandenhooff/keytree/mirror"
	"github.com/jellevandenhooff/keytree/trie"
	"github.com/jellevandenhooff/keytree/wire"
	"golang.org/x/net/context"
)

type tracker struct {
	ctx context.Context

	mirror *mirror.Mirror

	conn               *wire.KeyTreeClient
	server             *Server
	address, publicKey string

	// resolve queue
	queue chan crypto.Hash
}

func (t *tracker) FullSync(s *wire.SignedRoot, n *trie.Node) {
	t.server.considerTrie(t.publicKey, n, s)

	t.server.mu.Lock()
	localRoot := t.server.localTrie.root
	t.server.mu.Unlock()

	// todo: only reconcile updated nodes? some clever trie-based thing could be used for all three cases!

	_ = t.reconcile(localRoot, n, 0)
}

func (t *tracker) PartialSync(s *wire.SignedRoot, n *trie.Node) {
	t.server.considerTrie(t.publicKey, n, s)

	t.server.mu.Lock()
	localRoot := t.server.localTrie.root
	t.server.mu.Unlock()

	_ = t.reconcile(localRoot, n, 0)
}

func (t *tracker) Updated(s *wire.SignedRoot, n *trie.Node, u []*wire.TrieLeaf) {
	t.server.considerTrie(t.publicKey, n, s)

	for _, leaf := range u {
		t.queue <- leaf.NameHash
	}
}

func (t *tracker) fixer() error {
	for t.ctx.Err() == nil {
		select {
		case h := <-t.queue:
			t.fixup(h)
		case <-t.ctx.Done():
		}
	}
	return t.ctx.Err()
}

func (t *tracker) fixup(h crypto.Hash) {
	t.server.reconcileLocks.Lock(h)
	defer t.server.reconcileLocks.Unlock(h)

	localEntry, err := t.server.db.Read(h)
	if err != nil {
		log.Println(err)
		return
	}

	var since uint64
	if localEntry != nil {
		since = localEntry.Entry.Timestamp + 1
	}

	for {
		update, err := t.conn.History(h, since)
		if err == wire.ErrNotFound {
			break
		} else if err != nil {
			log.Println(err)
			return
		}

		// Try applying all updates in order. If it doesn't work, keep trying
		// anyway!
		if err := t.server.doUpdate(update); err != nil {
			log.Println(err)
		}
		since = update.Entry.Timestamp + 1
	}
}

func areSameEntry(local, remote *trie.Node) bool {
	if local == nil && remote.Entry != nil {
		return true
	}

	if remote == nil && local.Entry != nil {
		return true
	}

	return local != nil && local.Entry != nil &&
		remote != nil && remote.Entry != nil &&
		local.Entry.NameHash == remote.Entry.NameHash
}

func (t *tracker) reconcile(local, remote *trie.Node, depth int) error {
	if err := t.ctx.Err(); err != nil {
		return err
	}

	if remote == nil || local.Hash() == remote.Hash() {
		return nil
	}

	if areSameEntry(local, remote) {
		select {
		case t.queue <- remote.Entry.NameHash:
		case <-t.ctx.Done():
		}
	} else {
		localChildren := local.Split(depth)
		remoteChildren := remote.Split(depth)
		for i := 0; i < 2; i++ {
			if err := t.reconcile(localChildren[i], remoteChildren[i], depth+1); err != nil {
				return err
			}
		}
	}
	return nil
}

func runTracker(ctx context.Context, s *Server, address string, publicKey string) *tracker {
	log.Printf("spawning tracker for %s at %s", publicKey, address)

	conn := wire.NewKeyTreeClient("http://" + address)

	t := &tracker{
		ctx:       ctx,
		conn:      conn,
		server:    s,
		address:   address,
		publicKey: publicKey,
		queue:     make(chan crypto.Hash, reconcileQueueSize),
	}

	t.mirror = mirror.NewMirror(ctx, s.coordinator, conn, address, publicKey, nil, t)

	for i := 0; i < fixerParallelism; i++ {
		go t.fixer()
	}
	go t.mirror.Run()

	return t
}
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"image"
	"image/color"
	"os"
	"time"

	"github.com/bugsnag/bugsnag-go"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/model"
	owm "github.com/ninjasphere/openweathermap"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/O4b03b"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/clock"
	"github.com/ninjasphere/sphere-go-led-controller/util"
)

var enableWeatherPane = config.MustBool("led.weather.enabled")
var weatherUpdateInterval = config.MustDuration("led.weather.updateInterval")
var temperatureDisplayTime = config.Duration(time.Second*5, "led.weather.temperatureDisplayTime")

var globalSite *model.Site
var timezone *time.Location

type WeatherPane struct {
	siteModel   *ninja.ServiceClient
	site        *model.Site
	getWeather  *time.Timer
	tempTimeout *time.Timer
	temperature bool
	weather     *owm.CurrentWeatherData
	image       util.Image
}

func NewWeatherPane(conn *ninja.Connection) *WeatherPane {

	pane := &WeatherPane{
		siteModel: conn.GetServiceClient("$home/services/SiteModel"),
		image:     util.LoadImage(util.ResolveImagePath("weather/loading.gif")),
	}

	pane.tempTimeout = time.AfterFunc(0, func() {
		pane.temperature = false
	})

	if !enableWeatherPane {
		return pane
	}

	var err error
	pane.weather, err = owm.NewCurrent("C")
	if err != nil {
		log.Warningf("Failed to load weather api:", err)
		enableWeatherPane = false
	} else {
		go pane.GetWeather()
	}

	return pane
}

func (p *WeatherPane) GetWeather() {

	enableWeatherPane = false

	for {
		site := &model.Site{}
		err := p.siteModel.Call("fetch", config.MustString("siteId"), site, time.Second*5)

		if err == nil && (site.Longitude != nil || site.Latitude != nil) {
			p.site = site
			globalSite = site

			if site.TimeZoneID != nil {
				if timezone, err = time.LoadLocation(*site.TimeZoneID); err != nil {
					log.Warningf("error while setting timezone (%s): %s", *site.TimeZoneID, err)
					timezone, _ = time.LoadLocation("Local")
				}
			}
			break
		}

		log.Infof("Failed to get site, or site has no location.")

		time.Sleep(time.Second * 2)
	}

	for {

		p.weather.CurrentByCoordinates(
			&owm.Coordinates{
				Longitude: *p.site.Longitude,
				Latitude:  *p.site.Latitude,
			},
		)

		if len(p.weather.Weather) > 0 {

			filename := util.ResolveImagePath("weather/" + p.weather.Weather[0].Icon + ".png")

			if _, err := os.Stat(filename); os.IsNotExist(err) {
				enableWeatherPane = false
				fmt.Printf("Couldn't load image for weather: %s", filename)
				bugsnag.Notify(fmt.Errorf("Unknown weather icon: %s", filename), p.weather)
			} else {
				p.image = util.LoadImage(filename)
				enableWeatherPane = true
			}
		}

		time.Sleep(weatherUpdateInterval)

	}

}

func (p *WeatherPane) IsEnabled() bool {
	return enableWeatherPane && p.weather.Unit != ""
}

func (p *WeatherPane) Gesture(gesture *gestic.GestureMessage) {
	if gesture.Tap.Active() {
		log.Infof("Weather tap!")

		p.temperature = true
		p.tempTimeout.Reset(temperatureDisplayTime)
	}
}

func (p *WeatherPane) Render() (*image.RGBA, error) {
	if p.temperature {
		img := image.NewRGBA(image.Rect(0, 0, 16, 16))

		var temp string
		if p.weather.Sys.Country == "US" {
			temp = fmt.Sprintf("%dF", int(p.weather.Main.Temp*(9/5)-459.67))
		} else {
			temp = fmt.Sprintf("%dC", int(p.weather.Main.Temp-273.15))
		}

		width := clock.Font.DrawString(img, 0, 0, temp, color.Black)
		start := int((16 - width) / 2)

		O4b03b.Font.DrawString(img, start, 5, temp, color.RGBA{255, 255, 255, 255})

		return img, nil
	} else {
		return p.image.GetNextFrame(), nil
	}
}

func (p *WeatherPane) IsDirty() bool {
	return true
}
<commit_msg>Use forecast weather, show max+min on tap<commit_after>package ui

import (
	"fmt"
	"image"
	"image/color"
	"os"
	"time"

	"github.com/bugsnag/bugsnag-go"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/model"
	owm "github.com/ninjasphere/openweathermap"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/O4b03b"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/clock"
	"github.com/ninjasphere/sphere-go-led-controller/util"
)

var enableWeatherPane = config.MustBool("led.weather.enabled")
var weatherUpdateInterval = config.MustDuration("led.weather.updateInterval")
var temperatureDisplayTime = config.Duration(time.Second*5, "led.weather.temperatureDisplayTime")

var globalSite *model.Site
var timezone *time.Location

type WeatherPane struct {
	siteModel   *ninja.ServiceClient
	site        *model.Site
	getWeather  *time.Timer
	tempTimeout *time.Timer
	temperature bool
	weather     *owm.ForecastWeatherData
	image       util.Image
}

func NewWeatherPane(conn *ninja.Connection) *WeatherPane {

	pane := &WeatherPane{
		siteModel: conn.GetServiceClient("$home/services/SiteModel"),
		image:     util.LoadImage(util.ResolveImagePath("weather/loading.gif")),
	}

	pane.tempTimeout = time.AfterFunc(0, func() {
		pane.temperature = false
	})

	if !enableWeatherPane {
		return pane
	}

	var err error
	pane.weather, err = owm.NewForecast("C")
	if err != nil {
		log.Warningf("Failed to load weather api:", err)
		enableWeatherPane = false
	} else {
		go pane.GetWeather()
	}

	return pane
}

func (p *WeatherPane) GetWeather() {

	enableWeatherPane = false

	for {
		site := &model.Site{}
		err := p.siteModel.Call("fetch", config.MustString("siteId"), site, time.Second*5)

		if err == nil && (site.Longitude != nil || site.Latitude != nil) {
			p.site = site
			globalSite = site

			if site.TimeZoneID != nil {
				if timezone, err = time.LoadLocation(*site.TimeZoneID); err != nil {
					log.Warningf("error while setting timezone (%s): %s", *site.TimeZoneID, err)
					timezone, _ = time.LoadLocation("Local")
				}
			}
			break
		}

		log.Infof("Failed to get site, or site has no location.")

		time.Sleep(time.Second * 2)
	}

	for {

		p.weather.DailyByCoordinates(
			&owm.Coordinates{
				Longitude: *p.site.Longitude,
				Latitude:  *p.site.Latitude,
			},
			1,
		)

		if len(p.weather.List) > 0 {

			filename := util.ResolveImagePath("weather/" + p.weather.List[0].Weather[0].Icon + ".png")

			if _, err := os.Stat(filename); os.IsNotExist(err) {
				enableWeatherPane = false
				fmt.Printf("Couldn't load image for weather: %s", filename)
				bugsnag.Notify(fmt.Errorf("Unknown weather icon: %s", filename), p.weather)
			} else {
				p.image = util.LoadImage(filename)
				enableWeatherPane = true
			}
		}

		time.Sleep(weatherUpdateInterval)

	}

}

func (p *WeatherPane) IsEnabled() bool {
	return enableWeatherPane && p.weather.Unit != ""
}

func (p *WeatherPane) Gesture(gesture *gestic.GestureMessage) {
	if gesture.Tap.Active() {
		log.Infof("Weather tap!")

		p.temperature = true
		p.tempTimeout.Reset(temperatureDisplayTime)
	}
}

func (p *WeatherPane) Render() (*image.RGBA, error) {
	if p.temperature {
		img := image.NewRGBA(image.Rect(0, 0, 16, 16))

		drawText := func(text string, col color.RGBA, top int) {
			width := clock.Font.DrawString(img, 0, 8, text, color.Black)
			start := int(16 - width - 2)

			//spew.Dump("text", text, "width", width, "start", start)

			O4b03b.Font.DrawString(img, start, top, text, col)
		}

		if p.weather.City.Country == "US" || p.weather.City.Country == "United States of America" {
			drawText(fmt.Sprintf("%dF", int(p.weather.List[0].Temp.Max*(9/5)-459.67)), color.RGBA{253, 151, 32, 255}, 1)
			drawText(fmt.Sprintf("%dF", int(p.weather.List[0].Temp.Min*(9/5)-459.67)), color.RGBA{69, 175, 249, 255}, 8)
		} else {
			drawText(fmt.Sprintf("%dC", int(p.weather.List[0].Temp.Max*(9/5)-273.15)), color.RGBA{253, 151, 32, 255}, 1)
			drawText(fmt.Sprintf("%dC", int(p.weather.List[0].Temp.Min*(9/5)-273.15)), color.RGBA{69, 175, 249, 255}, 8)
		}

		return img, nil
	} else {
		return p.image.GetNextFrame(), nil
	}
}

func (p *WeatherPane) IsDirty() bool {
	return true
}
<|endoftext|>


<commit_before>package uniconfig

import (
	"flag"
	"os"
	"strings"
	"testing"
)

const ()

func AssertEquals(t *testing.T, actual, expected interface{}) {
	switch actual := actual.(type) {
	case string:
		if expected, ok := expected.(string); ok {
			if actual != expected {
				t.Fatalf("%s != %s", actual, expected)
			}
		} else {
			t.Fatalf("Cannot compare: %v and %v (not string)", actual, expected)
		}
	case int:
		if expected, ok := expected.(int); ok {
			if actual != expected {
				t.Fatalf("%d != %d", actual, expected)
			}
		} else {
			t.Fatalf("Cannot compare: %v and %v (not int)", actual, expected)
		}
	case bool:
		if expected, ok := expected.(bool); ok {
			if actual != expected {
				t.Fatalf("%s != %s", actual, expected)
			}
		} else {
			t.Fatalf("Cannot compare: %v and %v (not bool)", actual, expected)
		}
	default:
		t.Fatalf("Cannot compare: %v and %v", actual, expected)
	}
}

type TestConfig struct {
	Debug   bool
	Count   int `help:"number of items"`
	Nested1 struct {
		A       string
		B       string
		ignored string
	}
	Nested2 struct {
		Zzz bool
	}
	Count2 int64
}

func TestScanConfig(t *testing.T) {
	config := &TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"

	items := ScanConfig(config)
	AssertEquals(t, len(items), 6)
	AssertEquals(t, items[0].Section, "")
	AssertEquals(t, items[0].Name, "Debug")
	AssertEquals(t, items[0].Value.Interface(), false)
	AssertEquals(t, items[0].Help, "")
	AssertEquals(t, items[1].Section, "")
	AssertEquals(t, items[1].Name, "Count")
	AssertEquals(t, items[1].Value.Interface(), 42)
	AssertEquals(t, items[1].Help, "number of items")
	AssertEquals(t, items[2].Section, "Nested1")
	AssertEquals(t, items[2].Name, "A")
	AssertEquals(t, items[2].Value.Interface(), "")
	AssertEquals(t, items[3].Section, "Nested1")
	AssertEquals(t, items[3].Name, "B")
	AssertEquals(t, items[3].Value.Interface(), "baa")
	AssertEquals(t, items[4].Section, "Nested2")
	AssertEquals(t, items[4].Name, "Zzz")
	AssertEquals(t, items[4].Value.Interface(), false)
}

func TestLoadFromEnv(t *testing.T) {
	config := TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"
	items := ScanConfig(&config)

	os.Setenv("DEBUG", "true")
	os.Setenv("NESTED1_B", "buu")
	os.Setenv("NESTED1_A", "wtf")

	// need to reset the state between tests
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	InitFlags(items)
	LoadFromEnv(items)

	AssertEquals(t, config.Debug, true)
	AssertEquals(t, config.Count, 42)
	AssertEquals(t, config.Nested1.A, "wtf")
	AssertEquals(t, config.Nested1.B, "buu")
	AssertEquals(t, config.Nested2.Zzz, false)
}

func TestLoadFromIni(t *testing.T) {
	config := TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"
	items := ScanConfig(&config)

	// need to reset the state between tests
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	InitFlags(items)

	testIni := `
		debug = true
		count = 65535
		; this is a comment
		# also a comment

		[Nested1]
		A  = sometag
		irrelevant = ignored option

		number = 14
`

	dict := ParseIniFile(strings.NewReader(testIni))
	SetFromParsedIniFile(items, dict)
	AssertEquals(t, config.Debug, true)
	AssertEquals(t, config.Count, 65535)
	AssertEquals(t, config.Nested1.A, "sometag")
	AssertEquals(t, config.Nested1.B, "baa")
	AssertEquals(t, config.Nested2.Zzz, false)
}

func TestParseCmdline(t *testing.T) {
	args := []string{}
	configFile := GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "")
	args = []string{"--bubu", "config", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "")
	args = []string{"--test", "--config", "megaconfig", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "megaconfig")
	args = []string{"--test", "-config", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "megaconfig2 42")
	args = []string{"--test", "-config=\"4249\"", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "4249")
	args = []string{"--test", "--config=\"44991\"", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "44991")
}
<commit_msg>print stacktrace when test fails<commit_after>package uniconfig

import (
	"flag"
	"os"
	"runtime/debug"
	"strings"
	"testing"
)

const ()

func AssertEquals(t *testing.T, actual, expected interface{}) {
	switch actual := actual.(type) {
	case string:
		if expected, ok := expected.(string); ok {
			if actual != expected {
				debug.PrintStack()
				t.Fatalf("%s != %s", actual, expected)
			}
		} else {
			t.Fatalf("Cannot compare: %v and %v (not string)", actual, expected)
		}
	case int:
		if expected, ok := expected.(int); ok {
			if actual != expected {
				debug.PrintStack()
				t.Fatalf("%d != %d", actual, expected)
			}
		} else {
			debug.PrintStack()
			t.Fatalf("Cannot compare: %v and %v (not int)", actual, expected)
		}
	case bool:
		if expected, ok := expected.(bool); ok {
			if actual != expected {
				debug.PrintStack()
				t.Fatalf("%s != %s", actual, expected)
			}
		} else {
			debug.PrintStack()
			t.Fatalf("Cannot compare: %v and %v (not bool)", actual, expected)
		}
	default:
		debug.PrintStack()
		t.Fatalf("Cannot compare: %v and %v", actual, expected)
	}
}

type TestConfig struct {
	Debug   bool
	Count   int `help:"number of items"`
	Nested1 struct {
		A       string
		B       string
		ignored string
	}
	Nested2 struct {
		Zzz bool
	}
	Count2 int64
}

func TestScanConfig(t *testing.T) {
	config := &TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"

	items := ScanConfig(config)
	AssertEquals(t, len(items), 6)
	AssertEquals(t, items[0].Section, "")
	AssertEquals(t, items[0].Name, "Debug")
	AssertEquals(t, items[0].Value.Interface(), false)
	AssertEquals(t, items[0].Help, "")
	AssertEquals(t, items[1].Section, "")
	AssertEquals(t, items[1].Name, "Count")
	AssertEquals(t, items[1].Value.Interface(), 42)
	AssertEquals(t, items[1].Help, "number of items")
	AssertEquals(t, items[2].Section, "Nested1")
	AssertEquals(t, items[2].Name, "A")
	AssertEquals(t, items[2].Value.Interface(), "")
	AssertEquals(t, items[3].Section, "Nested1")
	AssertEquals(t, items[3].Name, "B")
	AssertEquals(t, items[3].Value.Interface(), "baa")
	AssertEquals(t, items[4].Section, "Nested2")
	AssertEquals(t, items[4].Name, "Zzz")
	AssertEquals(t, items[4].Value.Interface(), false)
}

func TestLoadFromEnv(t *testing.T) {
	config := TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"
	items := ScanConfig(&config)

	os.Setenv("DEBUG", "true")
	os.Setenv("NESTED1_B", "buu")
	os.Setenv("NESTED1_A", "wtf")

	// need to reset the state between tests
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	InitFlags(items)
	LoadFromEnv(items)

	AssertEquals(t, config.Debug, true)
	AssertEquals(t, config.Count, 42)
	AssertEquals(t, config.Nested1.A, "wtf")
	AssertEquals(t, config.Nested1.B, "buu")
	AssertEquals(t, config.Nested2.Zzz, false)
}

func TestLoadFromIni(t *testing.T) {
	config := TestConfig{}
	// some defaults
	config.Count = 42
	config.Nested1.B = "baa"
	items := ScanConfig(&config)

	// need to reset the state between tests
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	InitFlags(items)

	testIni := `
		debug = true
		count = 65535
		; this is a comment
		# also a comment

		[Nested1]
		A  = sometag

`

	dict := ParseIniFile(strings.NewReader(testIni))
	SetFromParsedIniFile(items, dict)
	AssertEquals(t, config.Debug, true)
	AssertEquals(t, config.Count, 65535)
	AssertEquals(t, config.Nested1.A, "sometag")
	AssertEquals(t, config.Nested1.B, "baa")
	AssertEquals(t, config.Nested2.Zzz, false)
}

func TestParseCmdline(t *testing.T) {
	args := []string{}
	configFile := GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "")
	args = []string{"--bubu", "config", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "")
	args = []string{"--test", "--config", "megaconfig", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "megaconfig")
	args = []string{"--test", "-config", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "megaconfig2 42")
	args = []string{"--test", "-config=\"4249\"", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "4249")
	args = []string{"--test", "--config=\"44991\"", "megaconfig2 42", "--bebe"}
	configFile = GetConfigPathFromCmd(args)
	AssertEquals(t, configFile, "44991")
}
<|endoftext|>