<commit_before>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	awscommon "github.com/mitchellh/packer/builder/amazon/common"
	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/packer"
)

type Config struct {
	Bucket       string `mapstructure:"bucket"`
	ManifestPath string `mapstructure:"manifest"`
	BoxName      string `mapstructure:"box_name"`
	BoxDir       string `mapstructure:"box_dir"`
	Version      string `mapstructure:"version"`

	common.PackerConfig    `mapstructure:",squash"`
	awscommon.AccessConfig `mapstructure:",squash"`
	tpl                    *packer.ConfigTemplate
}

type PostProcessor struct {
	config Config
	s3     *s3.Bucket
}

func (p *PostProcessor) Configure(raws ...interface{}) error {
	_, err := common.DecodeConfig(&p.config, raws...)
	if err != nil {
		return err
	}

	p.config.tpl, err = packer.NewConfigTemplate()
	if err != nil {
		return err
	}
	p.config.tpl.UserVars = p.config.PackerUserVars

	errs := &packer.MultiError{}

	errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(p.config.tpl)...)

	// required configuration
	templates := map[string]*string{
		"bucket":   &p.config.Bucket,
		"manifest": &p.config.ManifestPath,
		"box_name": &p.config.BoxName,
		"box_dir":  &p.config.BoxDir,
		"version":  &p.config.Version,
	}

	for key, ptr := range templates {
		if *ptr == "" {
			errs = packer.MultiErrorAppend(errs, fmt.Errorf("vagrant-s3 %s must be set", key))
		}
	}

	// Template process
	for key, ptr := range templates {
		*ptr, err = p.config.tpl.Process(*ptr, nil)
		if err != nil {
			errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", key, err))
		}
	}

	// setup the s3 bucket
	auth, err := p.config.AccessConfig.Auth()
	if err != nil {
		errs = packer.MultiErrorAppend(errs, err)
	}
	p.s3 = s3.New(auth, aws.USEast).Bucket(p.config.Bucket)

	if len(errs.Errors) > 0 {
		return errs
	}

	return nil
}

func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
	// Only accept input from the vagrant post-processor
	if artifact.BuilderId() != "mitchellh.post-processor.vagrant" {
		return nil, false, fmt.Errorf("Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId())
	}

	// Assume there is only one .box file to upload
	box := artifact.Files()[0]
	if !strings.HasSuffix(box, ".box") {
		return nil, false, fmt.Errorf("Unknown files in artifact from vagrant post-processor: %s", artifact.Files())
	}

	provider := providerFromBuilderName(artifact.Id())
	ui.Say(fmt.Sprintf("Preparing to upload box for '%s' provider to S3 bucket '%s'", provider, p.config.Bucket))

	// open the box so we can upload to S3 and calculate checksum for the manifest
	file, err := os.Open(box)
	if err != nil {
		return nil, false, err
	}
	defer file.Close()

	// get the file's size
	info, err := file.Stat()
	if err != nil {
		return nil, false, err
	}
	size := info.Size()
	ui.Message(fmt.Sprintf("Box to upload: %s (%d bytes)", box, size))

	// get the latest manifest so we can add to it
	ui.Message("Fetching latest manifest")
	manifest, err := p.getManifest()
	if err != nil {
		return nil, false, err
	}

	// generate the path to store the box in S3
	boxPath := fmt.Sprintf("%s/%s/%s", p.config.BoxDir, p.config.Version, path.Base(box))

	ui.Message("Generating checksum")
	checksum, err := sum256(file)
	if err != nil {
		return nil, false, err
	}
	ui.Message(fmt.Sprintf("Checksum is %s", checksum))

	ui.Message(fmt.Sprintf("Adding %s %s box to manifest", provider, p.config.Version))
	if err := manifest.add(p.config.Version, &Provider{
		Name:         provider,
		Url:          p.s3.URL(boxPath),
		ChecksumType: "sha256",
		Checksum:     checksum,
	}); err != nil {
		return nil, false, err
	}

	// upload the box to S3 (rewinding as we already read the file to generate the checksum)
	ui.Message(fmt.Sprintf("Uploading box to S3: %s", boxPath))
	if _, err := file.Seek(0, 0); err != nil {
		return nil, false, err
	}
	if size > 100*1024*1024 {
		ui.Message("File size > 100MB. Initiating multipart upload")
		multi, err := p.s3.Multi(boxPath, "application/octet-stream", "public-read")
		if err != nil {
			return nil, false, err
		}
		parts, err := multi.PutAll(file, 5*1024*1024)
		if err != nil {
			return nil, false, err
		}
		if err := multi.Complete(parts); err != nil {
			return nil, false, err
		}
	} else {
		if err := p.s3.PutReader(boxPath, file, size, "application/octet-stream", "public-read"); err != nil {
			return nil, false, err
		}
	}

	ui.Message(fmt.Sprintf("Uploading the manifest: %s", p.config.ManifestPath))
	if err := p.putManifest(manifest); err != nil {
		return nil, false, err
	}

	return &Artifact{p.s3.URL(p.config.ManifestPath)}, true, nil
}

func (p *PostProcessor) getManifest() (*Manifest, error) {
	body, err := p.s3.GetReader(p.config.ManifestPath)
	if err != nil {
		if s3Err, ok := err.(*s3.Error); ok && s3Err.Code == "NoSuchKey" {
			return &Manifest{Name: p.config.BoxName}, nil
		}
		return nil, err
	}
	defer body.Close()

	manifest := &Manifest{}
	if err := json.NewDecoder(body).Decode(manifest); err != nil {
		return nil, err
	}
	return manifest, nil
}

func (p *PostProcessor) putManifest(manifest *Manifest) error {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(manifest); err != nil {
		return err
	}
	if err := p.s3.Put(p.config.ManifestPath, buf.Bytes(), "application/json", "public-read"); err != nil {
		return err
	}
	return nil
}

// calculates a sha256 checksum of the file
func sum256(file *os.File) (string, error) {
	h := sha256.New()
	if _, err := io.Copy(h, file); err != nil {
		return "", err
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}

// converts a packer builder name to the corresponding vagrant provider
func providerFromBuilderName(name string) string {
	switch name {
	case "aws":
		return "aws"
	case "digitalocean":
		return "digitalocean"
	case "virtualbox":
		return "virtualbox"
	case "vmware":
		return "vmware_desktop"
	case "parallels":
		return "parallels"
	default:
		return name
	}
}
<commit_msg>Made AWS region setting a configurable property<commit_after>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	awscommon "github.com/mitchellh/packer/builder/amazon/common"
	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/packer"
)

type Config struct {
	Region       string `mapstructure:"region"`
	Bucket       string `mapstructure:"bucket"`
	ManifestPath string `mapstructure:"manifest"`
	BoxName      string `mapstructure:"box_name"`
	BoxDir       string `mapstructure:"box_dir"`
	Version      string `mapstructure:"version"`

	common.PackerConfig    `mapstructure:",squash"`
	awscommon.AccessConfig `mapstructure:",squash"`
	tpl                    *packer.ConfigTemplate
}

type PostProcessor struct {
	config Config
	s3     *s3.Bucket
}

func (p *PostProcessor) Configure(raws ...interface{}) error {
	_, err := common.DecodeConfig(&p.config, raws...)
	if err != nil {
		return err
	}

	p.config.tpl, err = packer.NewConfigTemplate()
	if err != nil {
		return err
	}
	p.config.tpl.UserVars = p.config.PackerUserVars

	errs := &packer.MultiError{}

	errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(p.config.tpl)...)

	// required configuration
	templates := map[string]*string{
		"region":   &p.config.Region,
		"bucket":   &p.config.Bucket,
		"manifest": &p.config.ManifestPath,
		"box_name": &p.config.BoxName,
		"box_dir":  &p.config.BoxDir,
		"version":  &p.config.Version,
	}

	for key, ptr := range templates {
		if *ptr == "" {
			errs = packer.MultiErrorAppend(errs, fmt.Errorf("vagrant-s3 %s must be set", key))
		}
	}

	// Template process
	for key, ptr := range templates {
		*ptr, err = p.config.tpl.Process(*ptr, nil)
		if err != nil {
			errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", key, err))
		}
	}

	// setup the s3 bucket
	auth, err := p.config.AccessConfig.Auth()
	if err != nil {
		errs = packer.MultiErrorAppend(errs, err)
	}

	// determine region
	region, valid := aws.Regions[p.config.Region]
	if valid {
		p.s3 = s3.New(auth, region).Bucket(p.config.Bucket)
	} else {
		errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid region specified: %s", p.config.Region))
	}

	if len(errs.Errors) > 0 {
		return errs
	}

	return nil
}

func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
	// Only accept input from the vagrant post-processor
	if artifact.BuilderId() != "mitchellh.post-processor.vagrant" {
		return nil, false, fmt.Errorf("Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId())
	}

	// Assume there is only one .box file to upload
	box := artifact.Files()[0]
	if !strings.HasSuffix(box, ".box") {
		return nil, false, fmt.Errorf("Unknown files in artifact from vagrant post-processor: %s", artifact.Files())
	}

	provider := providerFromBuilderName(artifact.Id())
	ui.Say(fmt.Sprintf("Preparing to upload box for '%s' provider to S3 bucket '%s'", provider, p.config.Bucket))

	// open the box so we can upload to S3 and calculate checksum for the manifest
	file, err := os.Open(box)
	if err != nil {
		return nil, false, err
	}
	defer file.Close()

	// get the file's size
	info, err := file.Stat()
	if err != nil {
		return nil, false, err
	}
	size := info.Size()
	ui.Message(fmt.Sprintf("Box to upload: %s (%d bytes)", box, size))

	// get the latest manifest so we can add to it
	ui.Message("Fetching latest manifest")
	manifest, err := p.getManifest()
	if err != nil {
		return nil, false, err
	}

	// generate the path to store the box in S3
	boxPath := fmt.Sprintf("%s/%s/%s", p.config.BoxDir, p.config.Version, path.Base(box))

	ui.Message("Generating checksum")
	checksum, err := sum256(file)
	if err != nil {
		return nil, false, err
	}
	ui.Message(fmt.Sprintf("Checksum is %s", checksum))

	ui.Message(fmt.Sprintf("Adding %s %s box to manifest", provider, p.config.Version))
	if err := manifest.add(p.config.Version, &Provider{
		Name:         provider,
		Url:          p.s3.URL(boxPath),
		ChecksumType: "sha256",
		Checksum:     checksum,
	}); err != nil {
		return nil, false, err
	}

	// upload the box to S3 (rewinding as we already read the file to generate the checksum)
	ui.Message(fmt.Sprintf("Uploading box to S3: %s", boxPath))
	if _, err := file.Seek(0, 0); err != nil {
		return nil, false, err
	}
	if size > 100*1024*1024 {
		ui.Message("File size > 100MB. Initiating multipart upload")
		multi, err := p.s3.Multi(boxPath, "application/octet-stream", "public-read")
		if err != nil {
			return nil, false, err
		}
		parts, err := multi.PutAll(file, 5*1024*1024)
		if err != nil {
			return nil, false, err
		}
		if err := multi.Complete(parts); err != nil {
			return nil, false, err
		}
	} else {
		if err := p.s3.PutReader(boxPath, file, size, "application/octet-stream", "public-read"); err != nil {
			return nil, false, err
		}
	}

	ui.Message(fmt.Sprintf("Uploading the manifest: %s", p.config.ManifestPath))
	if err := p.putManifest(manifest); err != nil {
		return nil, false, err
	}

	return &Artifact{p.s3.URL(p.config.ManifestPath)}, true, nil
}

func (p *PostProcessor) getManifest() (*Manifest, error) {
	body, err := p.s3.GetReader(p.config.ManifestPath)
	if err != nil {
		if s3Err, ok := err.(*s3.Error); ok && s3Err.Code == "NoSuchKey" {
			return &Manifest{Name: p.config.BoxName}, nil
		}
		return nil, err
	}
	defer body.Close()

	manifest := &Manifest{}
	if err := json.NewDecoder(body).Decode(manifest); err != nil {
		return nil, err
	}
	return manifest, nil
}

func (p *PostProcessor) putManifest(manifest *Manifest) error {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(manifest); err != nil {
		return err
	}
	if err := p.s3.Put(p.config.ManifestPath, buf.Bytes(), "application/json", "public-read"); err != nil {
		return err
	}
	return nil
}

// calculates a sha256 checksum of the file
func sum256(file *os.File) (string, error) {
	h := sha256.New()
	if _, err := io.Copy(h, file); err != nil {
		return "", err
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}

// converts a packer builder name to the corresponding vagrant provider
func providerFromBuilderName(name string) string {
	switch name {
	case "aws":
		return "aws"
	case "digitalocean":
		return "digitalocean"
	case "virtualbox":
		return "virtualbox"
	case "vmware":
		return "vmware_desktop"
	case "parallels":
		return "parallels"
	default:
		return name
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"math"
	"strings"
	"time"

	// "github.com/davecgh/go-spew/spew"
	"github.com/lucasb-eyer/go-colorful"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/clock"
	"github.com/ninjasphere/sphere-go-led-controller/ui"
	"github.com/ninjasphere/sphere-go-led-controller/util"
	"github.com/ninjasphere/usvc-lib/config"
)

var enableACPane = config.Bool(true, "ac.enable")
var tempAdjustInterval = config.Duration(time.Millisecond*300, "ac.airwheel.interval")
var airWheelReset = config.Duration(time.Millisecond*500, "ac.airwheel.reset")
var tapTimeout = config.Duration(time.Millisecond*500, "ac.tap.timeout")

var (
	red, _   = colorful.Hex("#fd9720")
	blue, _  = colorful.Hex("#45aff9")
	green, _ = colorful.Hex("#45f96b")
)

type ACPane struct {
	mode        string
	images      map[string]util.Image
	targetTemp  int
	currentTemp int
	flash       bool

	thermostat *ninja.ServiceClient
	acstat     *ninja.ServiceClient

	lastTempAdjust   time.Time
	lastAirWheelTime time.Time
	lastAirWheel     *uint8
	countSinceLast   int
	ignoringTap      bool
	ignoreTapTimer   *time.Timer
}

func NewACPane(conn *ninja.Connection) *ACPane {

	pane := &ACPane{
		mode:        "off",
		flash:       false,
		targetTemp:  666,
		currentTemp: 666,
		images: map[string]util.Image{
			"off":  util.LoadImage(util.ResolveImagePath("fan.gif")),
			"fan":  util.LoadImage(util.ResolveImagePath("fan-on.gif")),
			"cool": util.LoadImage(util.ResolveImagePath("fan-on-blue.gif")),
			"heat": util.LoadImage(util.ResolveImagePath("fan-on-red.gif")),
		},
	}

	pane.ignoreTapTimer = time.AfterFunc(0, func() {
		pane.ignoringTap = false
	})

	listening := make(map[string]bool)

	isFlash := func(s string) bool {
		return s == "SUSPENDED"
	}

	onState := func(protocol, event string, cb func(params *json.RawMessage)) {
		ui.GetChannelServicesContinuous("aircon", protocol, func(thing *model.Thing) bool {
			return true
		}, func(devices []*ninja.ServiceClient, err error) {
			if err != nil {
				log.Infof("Failed to update %s device: %s", protocol, err)
			} else {
				log.Infof("Got %d %s devices", len(devices), protocol)

				for _, device := range devices {

					log.Debugf("Checking %s device %s", protocol, device.Topic)

					if _, ok := listening[device.Topic]; !ok {

						// New device
						log.Infof("Got new %s device: %s", protocol, device.Topic)

						if protocol == "thermostat" {
							pane.thermostat = device
						}

						if protocol == "acstat" {
							pane.acstat = device
						}

						if protocol == "demand" {
							go func() {
								for {
									time.Sleep(time.Second)

									var state StateChangeNotification
									err := device.Call("getControlState", nil, &state, time.Second*10)
									if err != nil {
										log.Errorf("Failed to fetch state from demand channel: %s", err)

										if strings.Contains(err.Error(), "timed out") {
											continue
										} else {
											break
										}

									}

									// spew.Dump("Got demand state", state)

									pane.flash = isFlash(state.State)
								}
							}()
						}

						listening[device.Topic] = true

						device.OnEvent(event, func(params *json.RawMessage, values map[string]string) bool {
							cb(params)

							return true
						})
					}
				}
			}
		})
	}

	onState("temperature", "state", func(params *json.RawMessage) {
		var temp float64
		err := json.Unmarshal(*params, &temp)
		if err != nil {
			log.Infof("Failed to unmarshal temp from %s error:%s", *params, err)
		}

		pane.currentTemp = int(temp)

		log.Infof("Got the temp %d", pane.currentTemp)
	})

	onState("thermostat", "state", func(params *json.RawMessage) {
		var temp float64
		err := json.Unmarshal(*params, &temp)
		if err != nil {
			log.Infof("Failed to unmarshal thermostat from %s error:%s", *params, err)
		}

		pane.targetTemp = int(temp)

		log.Infof("Got the thermostat %d", pane.targetTemp)
	})

	onState("acstat", "state", func(params *json.RawMessage) {
		var state channels.ACState
		err := json.Unmarshal(*params, &state)
		if err != nil {
			log.Infof("Failed to unmarshal acstat from %s error:%s", *params, err)
		}

		pane.mode = *state.Mode

		log.Infof("Got the ac mode %d", pane.mode)
	})

	onState("demand", "controlstate", func(params *json.RawMessage) {

		// spew.Dump("demand/controlstate", params)

		var state StateChangeNotification
		err := json.Unmarshal(*params, &state)
		if err != nil {
			log.Infof("Failed to unmarshal demandstat state from %s error:%s", *params, err)
		}

		pane.flash = isFlash(state.State)

		log.Infof("Got the demandstat state %d", pane.mode)
	})

	go ui.StartSearchTasks(conn)

	return pane
}

type StateChangeNotification struct {
	State string `json:"state"` // the current state of the controller
}

func (p *ACPane) KeepAwake() bool {
	return true
}

func (p *ACPane) Locked() bool {
	return true
}

func (p *ACPane) IsEnabled() bool {
	return enableACPane
}

func (p *ACPane) Gesture(gesture *gestic.GestureMessage) {

	if p.acstat != nil && !p.ignoringTap && gesture.Tap.Active() {
		log.Infof("AC tap!")

		p.ignoringTap = true
		p.ignoreTapTimer.Reset(tapTimeout)

		switch p.mode {
		case "off":
			p.mode = "fan"
		case "fan":
			p.mode = "cool"
		case "cool":
			p.mode = "heat"
		case "heat":
			p.mode = "off"
		}

		p.acstat.Call("set", channels.ACState{
			Mode: &p.mode,
		}, nil, 0)

	}

	if p.thermostat != nil && p.targetTemp != 666 && p.lastAirWheel == nil || gesture.AirWheel.Counter != int(*p.lastAirWheel) {

		if time.Since(p.lastAirWheelTime) > airWheelReset {
			p.lastAirWheel = nil
		}

		if p.countSinceLast > gesture.AirWheel.CountSinceLast {
			p.lastAirWheel = nil
		}

		p.countSinceLast = gesture.AirWheel.CountSinceLast

		p.lastAirWheelTime = time.Now()

		log.Debugf("Airwheel: %d", gesture.AirWheel.Counter)

		if p.lastAirWheel != nil {
			offset := int(gesture.AirWheel.Counter) - int(*p.lastAirWheel)

			if offset > 30 {
				offset -= 255
			}

			if offset < -30 {
				offset += 255
			}

			log.Debugf("Airwheel New: %d Offset: %d Last: %d", gesture.AirWheel.Counter, offset, *p.lastAirWheel)

			log.Debugf("Current temp %f", p.targetTemp)

			log.Debugf("Temp offset %f", float64(offset)/255.0)

			if time.Since(p.lastTempAdjust) < tempAdjustInterval {
				log.Debugf("Temp rate limited")
			} else {
				p.lastTempAdjust = time.Now()
				log.Debugf("Temp NOT rate limited")
				if offset > 0 {
					p.targetTemp += 1
				} else {
					p.targetTemp -= 1
				}

				p.thermostat.Call("set", p.targetTemp, nil, 0)

			}

		}

		val := uint8(gesture.AirWheel.Counter)
		p.lastAirWheel = &val
	}
}

func (p *ACPane) Render() (*image.RGBA, error) {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	drawText := func(text string, col color.Color, x, y int) {
		clock.Font.DrawString(img, x, y, text, col)
	}

	draw.Draw(img, img.Bounds(), p.images[p.mode].GetNextFrame(), img.Bounds().Min, draw.Src)

	targetCol := green
	if p.flash {
		//drawText("$", green, 0, 11)
		h, s, v := targetCol.Hsv()
		targetCol = colorful.Hsv(sinTime(h), s, v)
	}

	if p.currentTemp != 666 {

		if p.currentTemp > p.targetTemp {
			drawText(fmt.Sprintf("%02d", p.currentTemp), red, 0, 11)
		} else if p.currentTemp < p.targetTemp {
			drawText(fmt.Sprintf("%02d", p.currentTemp), blue, 0, 11)
		}

	}

	if p.targetTemp != 666 {
		drawText(fmt.Sprintf("%02d", p.targetTemp), targetCol, 9, 11)
	}

	return img, nil
}

func (p *ACPane) IsDirty() bool {
	return true
}

func sinTime(x float64) float64 {
	t := float64(time.Now().Nanosecond()/int(time.Millisecond)) / 1000.0
	t = math.Sin((t - 0.5) * (2 * math.Pi))
	t = (t + 1) / 2
	return x * t
}
<commit_msg>Only poll for the initial state - listener will advise of future notifications.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"math"
	"time"

	// "github.com/davecgh/go-spew/spew"
	"github.com/lucasb-eyer/go-colorful"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/sphere-go-led-controller/fonts/clock"
	"github.com/ninjasphere/sphere-go-led-controller/ui"
	"github.com/ninjasphere/sphere-go-led-controller/util"
	"github.com/ninjasphere/usvc-lib/config"
)

var enableACPane = config.Bool(true, "ac.enable")
var tempAdjustInterval = config.Duration(time.Millisecond*300, "ac.airwheel.interval")
var airWheelReset = config.Duration(time.Millisecond*500, "ac.airwheel.reset")
var tapTimeout = config.Duration(time.Millisecond*500, "ac.tap.timeout")

var (
	red, _   = colorful.Hex("#fd9720")
	blue, _  = colorful.Hex("#45aff9")
	green, _ = colorful.Hex("#45f96b")
)

type ACPane struct {
	mode        string
	images      map[string]util.Image
	targetTemp  int
	currentTemp int
	flash       bool

	thermostat *ninja.ServiceClient
	acstat     *ninja.ServiceClient

	lastTempAdjust   time.Time
	lastAirWheelTime time.Time
	lastAirWheel     *uint8
	countSinceLast   int
	ignoringTap      bool
	ignoreTapTimer   *time.Timer
}

func NewACPane(conn *ninja.Connection) *ACPane {

	pane := &ACPane{
		mode:        "off",
		flash:       false,
		targetTemp:  666,
		currentTemp: 666,
		images: map[string]util.Image{
			"off":  util.LoadImage(util.ResolveImagePath("fan.gif")),
			"fan":  util.LoadImage(util.ResolveImagePath("fan-on.gif")),
			"cool": util.LoadImage(util.ResolveImagePath("fan-on-blue.gif")),
			"heat": util.LoadImage(util.ResolveImagePath("fan-on-red.gif")),
		},
	}

	pane.ignoreTapTimer = time.AfterFunc(0, func() {
		pane.ignoringTap = false
	})

	listening := make(map[string]bool)

	isFlash := func(s string) bool {
		return s == "SUSPENDED"
	}

	onState := func(protocol, event string, cb func(params *json.RawMessage)) {
		ui.GetChannelServicesContinuous("aircon", protocol, func(thing *model.Thing) bool {
			return true
		}, func(devices []*ninja.ServiceClient, err error) {
			if err != nil {
				log.Infof("Failed to update %s device: %s", protocol, err)
			} else {
				log.Infof("Got %d %s devices", len(devices), protocol)

				for _, device := range devices {

					log.Debugf("Checking %s device %s", protocol, device.Topic)

					if _, ok := listening[device.Topic]; !ok {

						// New device
						log.Infof("Got new %s device: %s", protocol, device.Topic)

						if protocol == "thermostat" {
							pane.thermostat = device
						}

						if protocol == "acstat" {
							pane.acstat = device
						}

						if protocol == "demand" {

							// only need to query for the initial notification - listener will pick up the rest

							go func() {
								var state StateChangeNotification
								err := device.Call("getControlState", nil, &state, time.Second*10)
								if err != nil {
									log.Errorf("Failed to fetch state from demand channel: %s", err)
								}

								// spew.Dump("Got demand state", state)

								pane.flash = isFlash(state.State)
							}()
						}

						listening[device.Topic] = true

						device.OnEvent(event, func(params *json.RawMessage, values map[string]string) bool {
							cb(params)

							return true
						})
					}
				}
			}
		})
	}

	onState("temperature", "state", func(params *json.RawMessage) {
		var temp float64
		err := json.Unmarshal(*params, &temp)
		if err != nil {
			log.Infof("Failed to unmarshal temp from %s error:%s", *params, err)
		}

		pane.currentTemp = int(temp)

		log.Infof("Got the temp %d", pane.currentTemp)
	})

	onState("thermostat", "state", func(params *json.RawMessage) {
		var temp float64
		err := json.Unmarshal(*params, &temp)
		if err != nil {
			log.Infof("Failed to unmarshal thermostat from %s error:%s", *params, err)
		}

		pane.targetTemp = int(temp)

		log.Infof("Got the thermostat %d", pane.targetTemp)
	})

	onState("acstat", "state", func(params *json.RawMessage) {
		var state channels.ACState
		err := json.Unmarshal(*params, &state)
		if err != nil {
			log.Infof("Failed to unmarshal acstat from %s error:%s", *params, err)
		}

		pane.mode = *state.Mode

		log.Infof("Got the ac mode %d", pane.mode)
	})

	onState("demand", "controlstate", func(params *json.RawMessage) {

		// spew.Dump("demand/controlstate", params)

		var state StateChangeNotification
		err := json.Unmarshal(*params, &state)
		if err != nil {
			log.Infof("Failed to unmarshal demandstat state from %s error:%s", *params, err)
		}

		pane.flash = isFlash(state.State)

		log.Infof("Got the demandstat state %d", pane.mode)
	})

	go ui.StartSearchTasks(conn)

	return pane
}

type StateChangeNotification struct {
	State string `json:"state"` // the current state of the controller
}

func (p *ACPane) KeepAwake() bool {
	return true
}

func (p *ACPane) Locked() bool {
	return true
}

func (p *ACPane) IsEnabled() bool {
	return enableACPane
}

func (p *ACPane) Gesture(gesture *gestic.GestureMessage) {

	if p.acstat != nil && !p.ignoringTap && gesture.Tap.Active() {
		log.Infof("AC tap!")

		p.ignoringTap = true
		p.ignoreTapTimer.Reset(tapTimeout)

		switch p.mode {
		case "off":
			p.mode = "fan"
		case "fan":
			p.mode = "cool"
		case "cool":
			p.mode = "heat"
		case "heat":
			p.mode = "off"
		}

		p.acstat.Call("set", channels.ACState{
			Mode: &p.mode,
		}, nil, 0)

	}

	if p.thermostat != nil && p.targetTemp != 666 && p.lastAirWheel == nil || gesture.AirWheel.Counter != int(*p.lastAirWheel) {

		if time.Since(p.lastAirWheelTime) > airWheelReset {
			p.lastAirWheel = nil
		}

		if p.countSinceLast > gesture.AirWheel.CountSinceLast {
			p.lastAirWheel = nil
		}

		p.countSinceLast = gesture.AirWheel.CountSinceLast

		p.lastAirWheelTime = time.Now()

		log.Debugf("Airwheel: %d", gesture.AirWheel.Counter)

		if p.lastAirWheel != nil {
			offset := int(gesture.AirWheel.Counter) - int(*p.lastAirWheel)

			if offset > 30 {
				offset -= 255
			}

			if offset < -30 {
				offset += 255
			}

			log.Debugf("Airwheel New: %d Offset: %d Last: %d", gesture.AirWheel.Counter, offset, *p.lastAirWheel)

			log.Debugf("Current temp %f", p.targetTemp)

			log.Debugf("Temp offset %f", float64(offset)/255.0)

			if time.Since(p.lastTempAdjust) < tempAdjustInterval {
				log.Debugf("Temp rate limited")
			} else {
				p.lastTempAdjust = time.Now()
				log.Debugf("Temp NOT rate limited")
				if offset > 0 {
					p.targetTemp += 1
				} else {
					p.targetTemp -= 1
				}

				p.thermostat.Call("set", p.targetTemp, nil, 0)

			}

		}

		val := uint8(gesture.AirWheel.Counter)
		p.lastAirWheel = &val
	}
}

func (p *ACPane) Render() (*image.RGBA, error) {

	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	drawText := func(text string, col color.Color, x, y int) {
		clock.Font.DrawString(img, x, y, text, col)
	}

	draw.Draw(img, img.Bounds(), p.images[p.mode].GetNextFrame(), img.Bounds().Min, draw.Src)

	targetCol := green
	if p.flash {
		//drawText("$", green, 0, 11)
		h, s, v := targetCol.Hsv()
		targetCol = colorful.Hsv(sinTime(h), s, v)
	}

	if p.currentTemp != 666 {

		if p.currentTemp > p.targetTemp {
			drawText(fmt.Sprintf("%02d", p.currentTemp), red, 0, 11)
		} else if p.currentTemp < p.targetTemp {
			drawText(fmt.Sprintf("%02d", p.currentTemp), blue, 0, 11)
		}

	}

	if p.targetTemp != 666 {
		drawText(fmt.Sprintf("%02d", p.targetTemp), targetCol, 9, 11)
	}

	return img, nil
}

func (p *ACPane) IsDirty() bool {
	return true
}

func sinTime(x float64) float64 {
	t := float64(time.Now().Nanosecond()/int(time.Millisecond)) / 1000.0
	t = math.Sin((t - 0.5) * (2 * math.Pi))
	t = (t + 1) / 2
	return x * t
}
<|endoftext|>


<commit_before>// Package main shows an example of gRPC handler usage.
package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"

	"github.com/c4milo/handlers/grpcutil"
	"github.com/c4milo/handlers/logger"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var usage = `
Usage:
example server               Runs server
example client               Runs client
`

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		fmt.Println(usage)
		os.Exit(1)
	}

	tlsKeyPair, err := tls.LoadX509KeyPair("testdata/cert.pem", "testdata/key.pem")
	if err != nil {
		panic(err)
	}

	if args[0] == "server" {
		server(tlsKeyPair)
		return
	}

	if args[0] == "client" {
		client(tlsKeyPair)
		return
	}

	log.Fatalf(usage)
	os.Exit(1)
}

type Service struct{}

func (s *Service) Hola(ctx context.Context, r *grpcutil.HolaRequest) (*grpcutil.HolaResponse, error) {
	return &grpcutil.HolaResponse{Greeting: "Hola from gRPC service!"}, nil
}

func server(cert tls.Certificate) {
	serverOpts := []grpc.ServerOption{
		grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
	}

	grpcServer := grpc.NewServer(serverOpts...)
	grpcutil.RegisterTestServer(grpcServer, new(Service))

	mux := http.DefaultServeMux
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Hola from HTTP handler!")
	})

	handler := logger.Handler(mux)
	handler = grpcutil.Handler(handler, grpcServer)

	srv := &http.Server{
		Addr:    "localhost:8080",
		Handler: handler,
		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
			NextProtos:   []string{"h2"},
		},
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for range c {
			srv.Close()
		}
	}()

	done := make(chan bool)
	fmt.Printf("Starting server in %q... ", srv.Addr)
	go func() {
		if err := srv.ListenAndServeTLS("", ""); err != nil {
			done <- true
			if err != http.ErrServerClosed {
				panic(err)
			}
		} else {
			done <- true
		}
	}()
	fmt.Println("done")
	<-done
}

func client(cert tls.Certificate) {
	x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		panic(err)
	}

	certPool := x509.NewCertPool()
	certPool.AddCert(x509Cert)

	clientCreds := credentials.NewClientTLSFromCert(certPool, "")
	clientOpts := []grpc.DialOption{
		grpc.WithTransportCredentials(clientCreds),
	}

	clientConn, err := grpc.Dial("localhost:8080", clientOpts...)
	if err != nil {
		panic(err)
	}

	defer clientConn.Close()

	test := grpcutil.NewTestClient(clientConn)
	res, err := test.Hola(context.Background(), &grpcutil.HolaRequest{})
	if err != nil {
		panic(err)
	}

	log.Println(res.Greeting)
}
<commit_msg>grpcutil: update code example<commit_after>// Package main shows an example of gRPC handler usage.
package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"

	"github.com/c4milo/handlers/grpcutil"
	"github.com/c4milo/handlers/logger"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var usage = `
Usage:
example server               Runs server
example client               Runs client
`

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		fmt.Println(usage)
		os.Exit(1)
	}

	tlsKeyPair, err := tls.LoadX509KeyPair("testdata/cert.pem", "testdata/key.pem")
	if err != nil {
		panic(err)
	}

	if args[0] == "server" {
		server(tlsKeyPair)
		return
	}

	if args[0] == "client" {
		client(tlsKeyPair)
		return
	}

	log.Fatalf(usage)
	os.Exit(1)
}

type Service struct{}

// Hola prints greeting message
func (s *Service) Hola(ctx context.Context, r *grpcutil.HolaRequest) (*grpcutil.HolaResponse, error) {
	return &grpcutil.HolaResponse{Greeting: "Hola from gRPC service!"}, nil
}

func registerService(binding grpcutil.ServiceBinding) error {
	grpcutil.RegisterTestServer(binding.GRPCServer, new(Service))
	return grpcutil.RegisterTestHandler(context.Background(), binding.GRPCGatewayMuxer, binding.GRPCGatewayClient)
}

func server(cert tls.Certificate) {
	mux := http.DefaultServeMux
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Hola from HTTP handler!")
	})

	handler := logger.Handler(mux)
	options := []grpcutil.Option{
		grpcutil.WithTLSCert(&cert),
		grpcutil.WithPort("8080"),
		grpcutil.WithServices([]grpcutil.ServiceRegisterFn{registerService}),
	}
	handler = grpcutil.Handler(handler, options...)

	srv := &http.Server{
		Addr:    "localhost:8080",
		Handler: handler,
		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
			NextProtos:   []string{"h2"},
		},
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for range c {
			srv.Close()
		}
	}()

	done := make(chan bool)
	fmt.Printf("Starting server in %q... ", srv.Addr)
	go func() {
		if err := srv.ListenAndServeTLS("", ""); err != nil {
			done <- true
			if err != http.ErrServerClosed {
				panic(err)
			}
		} else {
			done <- true
		}
	}()
	fmt.Println("done")
	<-done
}

func client(cert tls.Certificate) {
	x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		panic(err)
	}

	certPool := x509.NewCertPool()
	certPool.AddCert(x509Cert)

	clientCreds := credentials.NewClientTLSFromCert(certPool, "")
	clientOpts := []grpc.DialOption{
		grpc.WithTransportCredentials(clientCreds),
	}

	clientConn, err := grpc.Dial("localhost:8080", clientOpts...)
	if err != nil {
		panic(err)
	}

	defer clientConn.Close()

	test := grpcutil.NewTestClient(clientConn)
	res, err := test.Hola(context.Background(), &grpcutil.HolaRequest{})
	if err != nil {
		panic(err)
	}

	log.Println(res.Greeting)
}
<|endoftext|>


<commit_before>package config

// Config package contains the public config vars required in guble

import (
	"runtime"
	"strconv"

	"gopkg.in/alecthomas/kingpin.v2"
)

const (
	healthEndpointPrefix = "/_health"
)

var (
	Listen = kingpin.Flag("listen", "[Host:]Port the address to listen on (:8080)").
		Default(":8080").
		Short('l').
		Envar("GUBLE_LISTEN").
		String()
	StoragePath = kingpin.Flag("storage-path", "The path for storing messages and key value data if 'file' is enabled (/var/lib/guble)").
			Short('p').
			Envar("GUBLE_STORAGE_PATH").
			String()
	KVBackend = kingpin.Flag("kv-backend", "The storage backend for the key value store to use: file|memory (file)").
			Default("file").
			Envar("GUBLE_KV_BACKEND").
			String()
	MSBackend = kingpin.Flag("ms-backend", "The message storage backend : file|memory (file)").
			Default("file").
			Envar("GUBLE_MS_BACKEND").
			String()
	Health = kingpin.Flag("health", `The health endpoint (default: /_health; value for disabling it: "")`).
		Default(healthEndpointPrefix).
		Envar("GUBLE_HEALTH_ENDPOINT").
		String()
	Metrics = kingpin.Flag("metrics", "The metrics endpoint (disabled by default; a possible value for enabling it: /_metrics )").
		Envar("GUBLE_METRICS_ENDPOINT").
		String()

	GCM = struct {
		Enabled *bool
		APIKey  *string
		Workers *int
	}{
		Enabled: kingpin.Flag("gcm-enabled", "Enable the Google Cloud Messaging Connector").
			Envar("GUBLE_GCM_ENABLED").
			Bool(),
		APIKey: kingpin.Flag("gcm-api-key", "The Google API Key for Google Cloud Messaging").
			Envar("GUBLE_GCM_API_KEY").
			String(),
		Workers: kingpin.Flag("gcm-workers", "The number of workers handling traffic with Google Cloud Messaging (default: GOMAXPROCS)").
			Default(strconv.Itoa(runtime.GOMAXPROCS(0))).
			Envar("GUBLE_GCM_WORKERS").
			Int(),
	}

	Log = struct {
		Info, Debug *bool
	}{
		Info: kingpin.Flag("log-info", "Log on INFO level (false)").
			Default("false").
			Envar("GUBLE_LOG_INFO").
			Bool(),
		Debug: kingpin.Flag("log-debug", "Log on debug level (false)").
			Default("false").
			Envar("GUBLE_LOG_DEBUG").
			Bool(),
	}
)

func init() {
	kingpin.Parse()
}
<commit_msg>Config options comments<commit_after>package config

// Config package contains the public config vars required in guble

import (
	"runtime"
	"strconv"

	"gopkg.in/alecthomas/kingpin.v2"
)

const (
	healthEndpointPrefix = "/_health"
)

var (
	// Listen the address to bind the HTTP server to
	Listen = kingpin.Flag("listen", "[Host:]Port the address to listen on (:8080)").
		Default(":8080").
		Short('l').
		Envar("GUBLE_LISTEN").
		String()
	// StoragePath where to save the file system store
	StoragePath = kingpin.Flag("storage-path", "The path for storing messages and key value data if 'file' is enabled (/var/lib/guble)").
			Short('p').
			Envar("GUBLE_STORAGE_PATH").
			String()
	// KVBackend sets the key-value storage format to use
	KVBackend = kingpin.Flag("kv-backend", "The storage backend for the key value store to use: file|memory (file)").
			Default("file").
			Envar("GUBLE_KV_BACKEND").
			String()
	// MSBackend sets the message store format to use
	MSBackend = kingpin.Flag("ms-backend", "The message storage backend : file|memory (file)").
			Default("file").
			Envar("GUBLE_MS_BACKEND").
			String()
	// Health sets the health endpoint to bind in the HTTP server
	Health = kingpin.Flag("health", `The health endpoint (default: /_health; value for disabling it: "")`).
		Default(healthEndpointPrefix).
		Envar("GUBLE_HEALTH_ENDPOINT").
		String()
	// Metrics sets the metrics endpoint to bind in the HTTP server and return  metrics data
	Metrics = kingpin.Flag("metrics", "The metrics endpoint (disabled by default; a possible value for enabling it: /_metrics )").
		Envar("GUBLE_METRICS_ENDPOINT").
		String()

	// GCM settings related to activating the GCM connector module
	GCM = struct {
		Enabled *bool
		APIKey  *string
		Workers *int
	}{
		Enabled: kingpin.Flag("gcm-enabled", "Enable the Google Cloud Messaging Connector").
			Envar("GUBLE_GCM_ENABLED").
			Bool(),
		APIKey: kingpin.Flag("gcm-api-key", "The Google API Key for Google Cloud Messaging").
			Envar("GUBLE_GCM_API_KEY").
			String(),
		Workers: kingpin.Flag("gcm-workers", "The number of workers handling traffic with Google Cloud Messaging (default: GOMAXPROCS)").
			Default(strconv.Itoa(runtime.GOMAXPROCS(0))).
			Envar("GUBLE_GCM_WORKERS").
			Int(),
	}

	// Log settings
	Log = struct{ Info, Debug *bool }{
		Info: kingpin.Flag("log-info", "Log on INFO level (false)").
			Default("false").
			Envar("GUBLE_LOG_INFO").
			Bool(),
		Debug: kingpin.Flag("log-debug", "Log on debug level (false)").
			Default("false").
			Envar("GUBLE_LOG_DEBUG").
			Bool(),
	}
)

func init() {
	kingpin.Parse()
}
<|endoftext|>


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

package rafthttp

import (
	"time"

	"github.com/xiang90/probing"
)

var (
	// proberInterval must be shorter than read timeout.
	// Or the connection will time-out.
	proberInterval           = ConnReadTimeout - time.Second
	statusMonitoringInterval = 30 * time.Second
)

func addPeerToProber(p probing.Prober, id string, us []string) {
	hus := make([]string, len(us))
	for i := range us {
		hus[i] = us[i] + ProbingPrefix
	}

	p.AddHTTP(id, proberInterval, hus)

	s, err := p.Status(id)
	if err != nil {
		plog.Errorf("failed to add peer %s into prober", id)
	} else {
		go monitorProbingStatus(s, id)
	}
}

func monitorProbingStatus(s probing.Status, id string) {
	for {
		select {
		case <-time.After(statusMonitoringInterval):
			if !s.Health() {
				plog.Warningf("health check for peer %s could not connect", id)
			}
			if s.ClockDiff() > time.Second {
				plog.Warningf("the clock difference against peer %s is too high [%v > %v]", id, s.ClockDiff(), time.Second)
			}
			rtts.WithLabelValues(id).Observe(s.SRTT().Seconds())
		case <-s.StopNotify():
			return
		}
	}
}
<commit_msg>rafthttp: log health checking error early<commit_after>// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package rafthttp

import (
	"time"

	"github.com/xiang90/probing"
)

var (
	// proberInterval must be shorter than read timeout.
	// Or the connection will time-out.
	proberInterval           = ConnReadTimeout - time.Second
	statusMonitoringInterval = 30 * time.Second
	statusErrorInterval      = 5 * time.Second
)

func addPeerToProber(p probing.Prober, id string, us []string) {
	hus := make([]string, len(us))
	for i := range us {
		hus[i] = us[i] + ProbingPrefix
	}

	p.AddHTTP(id, proberInterval, hus)

	s, err := p.Status(id)
	if err != nil {
		plog.Errorf("failed to add peer %s into prober", id)
	} else {
		go monitorProbingStatus(s, id)
	}
}

func monitorProbingStatus(s probing.Status, id string) {
	// set the first interval short to log error early.
	interval := statusErrorInterval
	for {
		select {
		case <-time.After(interval):
			if !s.Health() {
				plog.Warningf("health check for peer %s could not connect: %v", id, s.Err())
				interval = statusErrorInterval
			} else {
				interval = statusMonitoringInterval
			}
			if s.ClockDiff() > time.Second {
				plog.Warningf("the clock difference against peer %s is too high [%v > %v]", id, s.ClockDiff(), time.Second)
			}
			rtts.WithLabelValues(id).Observe(s.SRTT().Seconds())
		case <-s.StopNotify():
			return
		}
	}
}
<|endoftext|>


<commit_before>package platform

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/grokify/gotilla/net/httputil"
	"github.com/grokify/ringcentral-sdk-go/rcsdk/core"
	rchttp "github.com/grokify/ringcentral-sdk-go/rcsdk/http"
)

const (
	ACCESS_TOKEN_TTL           int    = 3600
	REFRESH_TOKEN_TTL          int    = 36000  // 10 hours
	REFRESH_TOKEN_TTL_REMEMBER int    = 604800 // 1 week
	ACCOUNT_PREFIX             string = "/account/"
	ACCOUNT_ID                 string = "~"
	TOKEN_ENDPOINT             string = "/restapi/oauth/token"
	REVOKE_ENDPOINT            string = "/restapi/oauth/revoke"
	API_VERSION                string = "v1.0"
	URL_PREFIX                 string = "/restapi"
)

type Platform struct {
	server    string
	appKey    string
	appSecret string
	auth      Auth
	context   core.Context
}

func NewPlatform(context core.Context, appKey string, appSecret string, server string) Platform {
	p := Platform{appKey: appKey, appSecret: appSecret, server: server, auth: NewAuth()}
	p.auth = NewAuth()
	p.context = context
	return p
}

func (p *Platform) IsAuthorized() *Platform {
	return p
}

func (p *Platform) ApiUrl(url string) string {
	absUrl := strings.Join([]string{p.server, URL_PREFIX, "/", API_VERSION, url}, "")
	fmt.Println(absUrl)
	return absUrl
}

func (p *Platform) Authorize(username string, extension string, password string, remember bool) (*http.Response, error) {
	res, err := p.authCall(username, extension, password)
	if err == nil {
		data, err2 := httputil.ResponseBody(res)
		if err2 != nil {
			return res, err2
		}
		authData, err3 := NewAuthCallResponseData(data)
		if err3 != nil {
			return res, err3
		}
		p.auth.SetData(authData)
		fmt.Printf("[%v]\n", authData)
	}
	fmt.Println(res.Status)
	return res, err
}

func (p *Platform) GetApiKey() string {
	data := strings.Join([]string{p.appKey, p.appSecret}, ":")
	apiKey := base64.StdEncoding.EncodeToString([]byte(data))
	return apiKey
}

func (p *Platform) getAuthHeader() string {
	return strings.Join([]string{p.auth.GetTokenType(), p.auth.GetAccessToken()}, " ")
}

func (p *Platform) apiCall(req rchttp.Request) (*http.Response, error) {
	p.IsAuthorized()

	req.SetHeader("Authorization", p.getAuthHeader())
	req.SetUrl(p.ApiUrl(req.GetUrl()))
	return req.Send()
}

func (p *Platform) authCall(username string, extension string, password string) (*http.Response, error) {
	client := &http.Client{}

	// URL
	tokenUrl := strings.Join([]string{p.server, TOKEN_ENDPOINT}, "")

	// BODY
	data := url.Values{}
	data.Add("grant_type", "password")
	data.Add("username", username)
	data.Add("extension", extension)
	data.Add("password", password)
	body := strings.NewReader(data.Encode())

	// REQUEST
	req, _ := http.NewRequest("POST", tokenUrl, body)

	authzHeaderVal := strings.Join([]string{"Basic", p.GetApiKey()}, " ")
	req.Header.Add("Authorization", authzHeaderVal)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")

	// RESPONSE
	return client.Do(req)
}

func (p *Platform) Post(url string, queryParameters url.Values, body []byte, headers http.Header) (*http.Response, error) {
	rcreq := p.context.GetRequest("POST", url, queryParameters, body, headers)
	return p.apiCall(rcreq)
}

type AuthCallResponseData struct {
	AccessToken           string `json:"access_token"`
	TokenType             string `json:"token_type"`
	ExpiresIn             int    `json:"expires_in"`
	RefreshToken          string `json:"refresh_token"`
	RefreshTokenExpiresIn int    `json:"refresh_token_expires_in"`
	Scope                 string `json:"scope"`
	OwnerId               string `json:"owner_id"`
}

func NewAuthCallResponseData(body []byte) (AuthCallResponseData, error) {
	a := AuthCallResponseData{}
	err := json.Unmarshal(body, &a)
	return a, err
}
<commit_msg>cleanup fmt<commit_after>package platform

import (
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"
	"strings"

	"github.com/grokify/gotilla/net/httputil"
	"github.com/grokify/ringcentral-sdk-go/rcsdk/core"
	rchttp "github.com/grokify/ringcentral-sdk-go/rcsdk/http"
)

const (
	ACCESS_TOKEN_TTL           int    = 3600
	REFRESH_TOKEN_TTL          int    = 36000  // 10 hours
	REFRESH_TOKEN_TTL_REMEMBER int    = 604800 // 1 week
	ACCOUNT_PREFIX             string = "/account/"
	ACCOUNT_ID                 string = "~"
	TOKEN_ENDPOINT             string = "/restapi/oauth/token"
	REVOKE_ENDPOINT            string = "/restapi/oauth/revoke"
	API_VERSION                string = "v1.0"
	URL_PREFIX                 string = "/restapi"
)

type Platform struct {
	server    string
	appKey    string
	appSecret string
	auth      Auth
	context   core.Context
}

func NewPlatform(context core.Context, appKey string, appSecret string, server string) Platform {
	p := Platform{appKey: appKey, appSecret: appSecret, server: server, auth: NewAuth()}
	p.auth = NewAuth()
	p.context = context
	return p
}

func (p *Platform) IsAuthorized() *Platform {
	return p
}

func (p *Platform) ApiUrl(url string) string {
	absUrl := strings.Join([]string{p.server, URL_PREFIX, "/", API_VERSION, url}, "")
	return absUrl
}

func (p *Platform) Authorize(username string, extension string, password string, remember bool) (*http.Response, error) {
	res, err := p.authCall(username, extension, password)
	if err == nil {
		data, err2 := httputil.ResponseBody(res)
		if err2 != nil {
			return res, err2
		}
		authData, err3 := NewAuthCallResponseData(data)
		if err3 != nil {
			return res, err3
		}
		p.auth.SetData(authData)
	}
	return res, err
}

func (p *Platform) GetApiKey() string {
	data := strings.Join([]string{p.appKey, p.appSecret}, ":")
	apiKey := base64.StdEncoding.EncodeToString([]byte(data))
	return apiKey
}

func (p *Platform) getAuthHeader() string {
	return strings.Join([]string{p.auth.GetTokenType(), p.auth.GetAccessToken()}, " ")
}

func (p *Platform) apiCall(req rchttp.Request) (*http.Response, error) {
	p.IsAuthorized()

	req.SetHeader("Authorization", p.getAuthHeader())
	req.SetUrl(p.ApiUrl(req.GetUrl()))
	return req.Send()
}

func (p *Platform) authCall(username string, extension string, password string) (*http.Response, error) {
	client := &http.Client{}

	// URL
	tokenUrl := strings.Join([]string{p.server, TOKEN_ENDPOINT}, "")

	// BODY
	data := url.Values{}
	data.Add("grant_type", "password")
	data.Add("username", username)
	data.Add("extension", extension)
	data.Add("password", password)
	body := strings.NewReader(data.Encode())

	// REQUEST
	req, _ := http.NewRequest("POST", tokenUrl, body)

	authzHeaderVal := strings.Join([]string{"Basic", p.GetApiKey()}, " ")
	req.Header.Add("Authorization", authzHeaderVal)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")

	// RESPONSE
	return client.Do(req)
}

func (p *Platform) Post(url string, queryParameters url.Values, body []byte, headers http.Header) (*http.Response, error) {
	rcreq := p.context.GetRequest("POST", url, queryParameters, body, headers)
	return p.apiCall(rcreq)
}

type AuthCallResponseData struct {
	AccessToken           string `json:"access_token"`
	TokenType             string `json:"token_type"`
	ExpiresIn             int    `json:"expires_in"`
	RefreshToken          string `json:"refresh_token"`
	RefreshTokenExpiresIn int    `json:"refresh_token_expires_in"`
	Scope                 string `json:"scope"`
	OwnerId               string `json:"owner_id"`
}

func NewAuthCallResponseData(body []byte) (AuthCallResponseData, error) {
	a := AuthCallResponseData{}
	err := json.Unmarshal(body, &a)
	return a, err
}
<|endoftext|>


<commit_before>package sort

import (
	"reflect"
)

//QuickSort() is a custom quick sort.
//You should build your own compare function to sort in your own regulations.
func QuickSort(data interface{}, cmp func(i, j interface{}) bool) []interface{} {
	value := reflect.ValueOf(data)
	dataS := make([]interface{}, value.Len())
	for a := 0; a < value.Len(); a++ {
		dataS[a] = value.Index(a).Interface()
	}
	qsHandle(dataS, 0, len(dataS)-1, cmp)
	return dataS
}

func qsHandle(data []interface{}, left, right int, cmp func(i, j interface{}) bool) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if cmp(pivot, data[i]) {
				break
			}
			i++
		}

		for j > left {
			if cmp(data[j], pivot) {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandle(data, left, j-1, cmp)
	qsHandle(data, j+1, right, cmp)
}

//QuickSort for float32, sorting from smallest to biggest.
func QuickSortFloat32(data []float32) {
	qsHandleFloat32(data, 0, len(data)-1)
}

func qsHandleFloat32(data []float32, left, right int) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleFloat32(data, left, j-1)
	qsHandleFloat32(data, j+1, right)
}

//QuickSort for float64, sorting from smallest to biggest.
func QuickSortFloat64(data []float64) {
	qsHandleFloat64(data, 0, len(data)-1)
}

func qsHandleFloat64(data []float64, left, right int) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleFloat64(data, left, j-1)
	qsHandleFloat64(data, j+1, right)
}

//QuickSort for int, sorting from smallest to biggest.
func QuickSortInt(data []int) {
	qsHandleInt(data, 0, len(data)-1)
}

func qsHandleInt(data []int, left, right int) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt(data, left, j-1)
	qsHandleInt(data, j+1, right)
}

//QuickSort for int32, sorting from smallest to biggest.
func QuickSortInt32(data []int32) {
	qsHandleInt32(data, 0, len(data)-1)
}

func qsHandleInt32(data []int32, left, right int) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt32(data, left, j-1)
	qsHandleInt32(data, j+1, right)
}

//QuickSort for int64, sorting from smallest to biggest.
func QuickSortInt64(data []int64) {
	qsHandleInt64(data, 0, len(data)-1)
}

func qsHandleInt64(data []int64, left, right int) {
	if left >= right {
		return
	}

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt64(data, left, j-1)
	qsHandleInt64(data, j+1, right)
}
<commit_msg>Q sort add self balence<commit_after>package sort

import (
	"math/rand"
	"reflect"
	"time"
)

//QuickSort() is a custom quick sort.
//You should build your own compare function to sort in your own regulations.
func QuickSort(data interface{}, cmp func(i, j interface{}) bool) []interface{} {
	value := reflect.ValueOf(data)
	dataS := make([]interface{}, value.Len())
	for a := 0; a < value.Len(); a++ {
		dataS[a] = value.Index(a).Interface()
	}
	qsHandle(dataS, 0, len(dataS)-1, cmp)
	return dataS
}

func qsHandle(data []interface{}, left, right int, cmp func(i, j interface{}) bool) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if cmp(pivot, data[i]) {
				break
			}
			i++
		}

		for j > left {
			if cmp(data[j], pivot) {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandle(data, left, j-1, cmp)
	qsHandle(data, j+1, right, cmp)
}

//QuickSort for float32, sorting from smallest to biggest.
func QuickSortFloat32(data []float32) {
	qsHandleFloat32(data, 0, len(data)-1)
}

func qsHandleFloat32(data []float32, left, right int) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleFloat32(data, left, j-1)
	qsHandleFloat32(data, j+1, right)
}

//QuickSort for float64, sorting from smallest to biggest.
func QuickSortFloat64(data []float64) {
	qsHandleFloat64(data, 0, len(data)-1)
}

func qsHandleFloat64(data []float64, left, right int) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleFloat64(data, left, j-1)
	qsHandleFloat64(data, j+1, right)
}

//QuickSort for int, sorting from smallest to biggest.
func QuickSortInt(data []int) {
	qsHandleInt(data, 0, len(data)-1)
}

func qsHandleInt(data []int, left, right int) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt(data, left, j-1)
	qsHandleInt(data, j+1, right)
}

//QuickSort for int32, sorting from smallest to biggest.
func QuickSortInt32(data []int32) {
	qsHandleInt32(data, 0, len(data)-1)
}

func qsHandleInt32(data []int32, left, right int) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt32(data, left, j-1)
	qsHandleInt32(data, j+1, right)
}

//QuickSort for int64, sorting from smallest to biggest.
func QuickSortInt64(data []int64) {
	qsHandleInt64(data, 0, len(data)-1)
}

func qsHandleInt64(data []int64, left, right int) {
	if left >= right {
		return
	}

	rand.Seed(time.Now().Unix())
	randPivot := rand.Int() % (right - left)
	data[left], data[randPivot] = data[randPivot], data[left]

	pivot := data[left]
	i, j := left+1, right

	for true {
		for i <= right {
			if data[i] > pivot {
				break
			}
			i++
		}

		for j > left {
			if data[j] < pivot {
				break
			}
			j = j - 1
		}

		if i > j {
			break
		} else {
			data[i], data[j] = data[j], data[i]
		}
	}

	data[left], data[j] = data[j], data[left]

	qsHandleInt64(data, left, j-1)
	qsHandleInt64(data, j+1, right)
}
<|endoftext|>


<commit_before>// +build !windows

package supervisor

import (
	"os"
	"os/exec"
	"syscall"
	"testing"
	"time"

	"github.com/mackerelio/mackerel-agent/pidfile"
)

const stubAgent = "testdata/stub-agent"

func init() {
	err := exec.Command("go", "build", "-o", stubAgent, "testdata/stub-agent.go").Run()
	if err != nil {
		panic(err)
	}
}

func TestSupervise(t *testing.T) {
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- Supervise(stubAgent, []string{"dummy"}, ch)
	}()
	time.Sleep(50 * time.Millisecond)
	ch <- os.Interrupt
	err := <-done
	if err != nil {
		t.Errorf("error should be nil but: %v", err)
	}
}

func TestSupervisor(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"dummy"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	pid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(pid) {
		t.Errorf("process doesn't exist")
	}
	time.Sleep(50 * time.Millisecond)
	ch <- os.Interrupt

	err := <-done
	if err != nil {
		t.Errorf("error should be nil but: %v", err)
	}
	if pidfile.ExistsPid(pid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_reload(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"dummy"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	ch <- syscall.SIGHUP
	time.Sleep(200 * time.Millisecond)
	newPid := sv.getCmd().Process.Pid
	if oldPid == newPid {
		t.Errorf("reload failed")
	}
	if pidfile.ExistsPid(oldPid) {
		t.Errorf("old process isn't terminated")
	}
	if !pidfile.ExistsPid(newPid) {
		t.Errorf("new process doesn't exist")
	}
	ch <- syscall.SIGTERM
	err := <-done
	if err != nil {
		t.Errorf("something went wrong")
	}
	if newPid != sv.getCmd().Process.Pid {
		t.Errorf("something went wrong")
	}
	if pidfile.ExistsPid(newPid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_reloadFail(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"failed"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	ch <- syscall.SIGHUP
	time.Sleep(time.Second)
	newPid := sv.getCmd().Process.Pid
	if oldPid != newPid {
		t.Errorf("reload should be failed, but unintentionally reloaded")
	}

	ch <- syscall.SIGTERM
	<-done
}

func TestSupervisor_launchFailed(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"launch failure"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	pid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(pid) {
		t.Errorf("process doesn't exist")
	}
	err := <-done
	if err == nil {
		t.Errorf("something went wrong")
	}
	if pidfile.ExistsPid(sv.getCmd().Process.Pid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_crashRecovery(t *testing.T) {
	origSpawnInterval := spawnInterval
	spawnInterval = 300 * time.Millisecond
	defer func() { spawnInterval = origSpawnInterval }()

	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"blah blah blah"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	time.Sleep(spawnInterval)

	// let it crash
	sv.getCmd().Process.Signal(syscall.SIGUSR1)

	time.Sleep(spawnInterval)
	newPid := sv.getCmd().Process.Pid
	if oldPid == newPid {
		t.Errorf("crash recovery failed")
	}
	if pidfile.ExistsPid(oldPid) {
		t.Errorf("old process isn't terminated")
	}
	if !pidfile.ExistsPid(newPid) {
		t.Errorf("new process doesn't exist")
	}
	ch <- syscall.SIGTERM
	<-done
}
<commit_msg>overwrite os.Args[0] to make pid check pass in tests for suprevisor<commit_after>// +build !windows

package supervisor

import (
	"os"
	"os/exec"
	"syscall"
	"testing"
	"time"

	"github.com/mackerelio/mackerel-agent/pidfile"
)

const stubAgent = "testdata/stub-agent"

func init() {
	err := exec.Command("go", "build", "-o", stubAgent, "testdata/stub-agent.go").Run()
	if err != nil {
		panic(err)
	}
}

func TestMain(m *testing.M) {
	// overwrite os.Args[0] to make pid check pass
	oldArg0 := os.Args[0]
	os.Args[0] = stubAgent
	defer func() { os.Args[0] = oldArg0 }()
	os.Exit(m.Run())
}

func TestSupervise(t *testing.T) {
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- Supervise(stubAgent, []string{"dummy"}, ch)
	}()
	time.Sleep(50 * time.Millisecond)
	ch <- os.Interrupt
	err := <-done
	if err != nil {
		t.Errorf("error should be nil but: %v", err)
	}
}

func TestSupervisor(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"dummy"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	pid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(pid) {
		t.Errorf("process doesn't exist")
	}
	time.Sleep(50 * time.Millisecond)
	ch <- os.Interrupt

	err := <-done
	if err != nil {
		t.Errorf("error should be nil but: %v", err)
	}
	if pidfile.ExistsPid(pid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_reload(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"dummy"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	ch <- syscall.SIGHUP
	time.Sleep(200 * time.Millisecond)
	newPid := sv.getCmd().Process.Pid
	if oldPid == newPid {
		t.Errorf("reload failed")
	}
	if pidfile.ExistsPid(oldPid) {
		t.Errorf("old process isn't terminated")
	}
	if !pidfile.ExistsPid(newPid) {
		t.Errorf("new process doesn't exist")
	}
	ch <- syscall.SIGTERM
	err := <-done
	if err != nil {
		t.Errorf("something went wrong")
	}
	if newPid != sv.getCmd().Process.Pid {
		t.Errorf("something went wrong")
	}
	if pidfile.ExistsPid(newPid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_reloadFail(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"failed"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	ch <- syscall.SIGHUP
	time.Sleep(time.Second)
	newPid := sv.getCmd().Process.Pid
	if oldPid != newPid {
		t.Errorf("reload should be failed, but unintentionally reloaded")
	}

	ch <- syscall.SIGTERM
	<-done
}

func TestSupervisor_launchFailed(t *testing.T) {
	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"launch failure"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	pid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(pid) {
		t.Errorf("process doesn't exist")
	}
	err := <-done
	if err == nil {
		t.Errorf("something went wrong")
	}
	if pidfile.ExistsPid(sv.getCmd().Process.Pid) {
		t.Errorf("child process isn't terminated")
	}
}

func TestSupervisor_crashRecovery(t *testing.T) {
	origSpawnInterval := spawnInterval
	spawnInterval = 300 * time.Millisecond
	defer func() { spawnInterval = origSpawnInterval }()

	sv := &supervisor{
		prog: stubAgent,
		argv: []string{"blah blah blah"},
	}
	ch := make(chan os.Signal, 1)
	done := make(chan error)
	go func() {
		done <- sv.supervise(ch)
	}()
	time.Sleep(50 * time.Millisecond)
	oldPid := sv.getCmd().Process.Pid
	if !pidfile.ExistsPid(oldPid) {
		t.Errorf("process doesn't exist")
	}
	time.Sleep(spawnInterval)

	// let it crash
	sv.getCmd().Process.Signal(syscall.SIGUSR1)

	time.Sleep(spawnInterval)
	newPid := sv.getCmd().Process.Pid
	if oldPid == newPid {
		t.Errorf("crash recovery failed")
	}
	if pidfile.ExistsPid(oldPid) {
		t.Errorf("old process isn't terminated")
	}
	if !pidfile.ExistsPid(newPid) {
		t.Errorf("new process doesn't exist")
	}
	ch <- syscall.SIGTERM
	<-done
}
<|endoftext|>


<commit_before>package boltrepository

import (
	"encoding/json"
	"fmt"
	"github.com/boltdb/bolt"
)

type Repository struct {
	FilePath  string
	GetObject func([]byte) interface{}
}

func NewRepository(filePath string) *Repository {
	repository := &Repository{
		FilePath: filePath,
	}

	db, _ := bolt.Open(repository.FilePath, 0644, nil)
	db.Close()

	return repository
}

func (r *Repository) Deserialize(jsonBytes []byte, target interface{}) error {

	err := json.Unmarshal(jsonBytes, &target)

	return err
}

func (r *Repository) ForEach(bucketName string, action func(string, interface{})) error {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		bucket.ForEach(func(k, v []byte) error {

			key := string(k)
			value := r.GetObject(v)

			action(key, value)

			return nil
		})
		return nil

	})

	return err
}

func (r *Repository) HasItem(bucketName string, keyName string) (hasItem bool, err error) {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return false, err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		hasItem = val != nil

		return nil
	})

	return hasItem, err
}

func (r *Repository) Read(bucketName string, keyName string) (obj interface{}, err error) {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return nil, err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		obj = r.GetObject(val)

		return nil
	})

	return obj, err
}

func (r *Repository) ReadInto(bucketName string, keyName string, target interface{}) error {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		return r.Deserialize(val, &target)
	})

	return err
}

func (r *Repository) Save(bucketName string, keyName string, value interface{}) error {

	db, err := bolt.Open(r.FilePath, 0644, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	serialized, _ := r.Serialize(value)

	// store some data
	err = db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		if err != nil {
			return err
		}

		err = bucket.Put([]byte(keyName), serialized)
		if err != nil {
			return err
		}
		return nil
	})

	return err
}

func (r *Repository) Serialize(value interface{}) ([]byte, error) {

	jsonBytes, err := json.Marshal(value)

	return jsonBytes, err
}
<commit_msg>Delete<commit_after>package boltrepository

import (
	"encoding/json"
	"fmt"
	"github.com/boltdb/bolt"
)

type Repository struct {
	FilePath  string
	GetObject func([]byte) interface{}
}

func NewRepository(filePath string) *Repository {
	repository := &Repository{
		FilePath: filePath,
	}

	db, _ := bolt.Open(repository.FilePath, 0644, nil)
	db.Close()

	return repository
}

func (r *Repository) Delete(bucketName string, keyName string) error {

	db, err := bolt.Open(r.FilePath, 0644, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	err = db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		if err != nil {
			return err
		}

		err = bucket.Delete([]byte(keyName))
		if err != nil {
			return err
		}
		return nil
	})

	return err
}

func (r *Repository) Deserialize(jsonBytes []byte, target interface{}) error {

	err := json.Unmarshal(jsonBytes, &target)

	return err
}

func (r *Repository) ForEach(bucketName string, action func(string, interface{})) error {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		bucket.ForEach(func(k, v []byte) error {

			key := string(k)
			value := r.GetObject(v)

			action(key, value)

			return nil
		})
		return nil

	})

	return err
}

func (r *Repository) HasItem(bucketName string, keyName string) (hasItem bool, err error) {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return false, err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		hasItem = val != nil

		return nil
	})

	return hasItem, err
}

func (r *Repository) Read(bucketName string, keyName string) (obj interface{}, err error) {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return nil, err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		obj = r.GetObject(val)

		return nil
	})

	return obj, err
}

func (r *Repository) ReadInto(bucketName string, keyName string, target interface{}) error {

	db, err := bolt.Open(r.FilePath, 0644, &bolt.Options{ReadOnly: true})
	if err != nil {
		return err
	}
	defer db.Close()

	// retrieve the data
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return fmt.Errorf("Bucket %q not found!", []byte(bucketName))
		}

		val := bucket.Get([]byte(keyName))

		return r.Deserialize(val, &target)
	})

	return err
}

func (r *Repository) Save(bucketName string, keyName string, value interface{}) error {

	db, err := bolt.Open(r.FilePath, 0644, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	serialized, _ := r.Serialize(value)

	// store some data
	err = db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		if err != nil {
			return err
		}

		err = bucket.Put([]byte(keyName), serialized)
		if err != nil {
			return err
		}
		return nil
	})

	return err
}

func (r *Repository) Serialize(value interface{}) ([]byte, error) {

	jsonBytes, err := json.Marshal(value)

	return jsonBytes, err
}
<|endoftext|>


<commit_before>package handler_test

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/m-lab/annotation-service/api"
	"github.com/m-lab/annotation-service/geolite2"
	"github.com/m-lab/annotation-service/handler"
	"github.com/m-lab/annotation-service/manager"
)

func init() {
	// Always prepend the filename and line number.
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func TestAnnotate(t *testing.T) {
	tests := []struct {
		ip   string
		time string
		res  string
	}{
		{"1.4.128.0", "625600", `{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":42.1,"longitude":-73.1},"ASN":{}}`},
		{"This will be an error.", "1000", "Invalid request"},
	}
	// TODO - make and use an annotator generator
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
				Latitude:      42.1,
				Longitude:     -73.1,
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
				Latitude:      42.1,
				Longitude:     -73.1,
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City", RegionCode: "ME",
			},
		},
	}
	for _, test := range tests {
		w := httptest.NewRecorder()
		r := &http.Request{}
		r.URL, _ = url.Parse("/annotate?ip_addr=" + url.QueryEscape(test.ip) + "&since_epoch=" + url.QueryEscape(test.time))
		handler.Annotate(w, r)
		body := w.Body.String()
		if string(body) != test.res {
			t.Errorf("\nGot\n__%s__\nexpected\n__%s__\n", body, test.res)
		}
	}
}

func TestValidateAndParse(t *testing.T) {
	tests := []struct {
		req *http.Request
		res *api.RequestData
		err error
	}{
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=127.0.0.1&since_epoch=fail", nil),
			res: nil,
			err: errors.New("invalid time"),
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=fail&since_epoch=10", nil),
			res: nil,
			err: errors.New("invalid IP address"),
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=127.0.0.1&since_epoch=10", nil),
			res: &api.RequestData{IP: "127.0.0.1", IPFormat: 4, Timestamp: time.Unix(10, 0)},
			err: nil,
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=2620:0:1003:1008:5179:57e3:3c75:1886&since_epoch=10", nil),
			res: &api.RequestData{IP: "2620:0:1003:1008:5179:57e3:3c75:1886", IPFormat: 6, Timestamp: time.Unix(10, 0)},
			err: nil,
		},
	}
	for _, test := range tests {
		res, err := handler.ValidateAndParse(test.req)
		if !reflect.DeepEqual(res, test.res) {
			t.Errorf("Expected %+v, got %+v.", test.res, res)
		}
		if !reflect.DeepEqual(err, test.err) {
			t.Errorf("Expected %+v, got %+v.", test.err, err)
		}
	}

}

type badReader int

func (badReader) Read(_ []byte) (n int, err error) {
	return 0, errors.New("Bad Reader")
}

func TestBatchValidateAndParse(t *testing.T) {
	timeCon, _ := time.Parse(time.RFC3339, "2002-10-02T15:00:00Z")
	tests := []struct {
		source io.Reader
		res    []api.RequestData
		err    error
	}{
		{
			source: badReader(0),
			res:    nil,
			err:    errors.New("Bad Reader"),
		},
		{
			source: bytes.NewBufferString(`{`),
			res:    nil,
			err:    errors.New("unexpected end of JSON input"),
		},
		{
			source: bytes.NewBufferString(`[]`),
			res:    []api.RequestData{},
			err:    nil,
		},
		{
			source: bytes.NewBufferString(`[{"ip": "Bad IP", "timestamp": "2002-10-02T15:00:00Z"}]`),
			res:    nil,
			err:    errors.New("invalid IP address"),
		},
		{
			source: bytes.NewBufferString(`[{"ip": "127.0.0.1", "timestamp": "2002-10-02T15:00:00Z"},` +
				`{"ip": "2620:0:1003:1008:5179:57e3:3c75:1886", "timestamp": "2002-10-02T15:00:00Z"}]`),
			res: []api.RequestData{
				{IP: "127.0.0.1", IPFormat: 4, Timestamp: timeCon},
				{IP: "2620:0:1003:1008:5179:57e3:3c75:1886", IPFormat: 6, Timestamp: timeCon},
			},
			err: nil,
		},
	}
	for i, test := range tests {
		jsonBuffer, err := ioutil.ReadAll(test.source)
		if err != nil {
			if err.Error() != test.err.Error() {
				log.Printf("Expected %T\n", test.err)
				t.Error(err)
			}
			continue
		}
		res, err := handler.BatchValidateAndParse(jsonBuffer)
		if !reflect.DeepEqual(res, test.res) {
			t.Errorf("Test %d: Expected %+v, got %+v.", i, test.res, res)
		}
		if err != nil && test.err == nil || err == nil && test.err != nil {
			t.Errorf("Test %d: Expected %+v, got %+v.", i, test.err, err)
			continue
		}
		if err != nil && test.err != nil && err.Error() != test.err.Error() {
			t.Errorf("Test %d: Expected %+v, got %+v.", i, test.err, err)
			continue
		}
	}

}

func TestBatchAnnotate(t *testing.T) {
	tests := []struct {
		body string
		res  string
		alt  string // Alternate valid result
	}{
		{
			body: "{",
			res:  "Invalid Request!",
			alt:  "",
		},
		{
			body: `[{"ip": "127.0.0.1", "timestamp": "2017-08-25T13:31:12.149678161-04:00"},
                               {"ip": "2620:0:1003:1008:5179:57e3:3c75:1886", "timestamp": "2017-08-25T14:32:13.149678161-04:00"}]`,
			res: `{"127.0.0.1ov94o0":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583"},"ASN":{}},"2620:0:1003:1008:5179:57e3:3c75:1886ov97hp":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583"},"ASN":{}}}`,
			// TODO - remove alt after updating json annotations to omitempty.
			alt: `{"127.0.0.1ov94o0":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":0,"longitude":0},"ASN":{}},"2620:0:1003:1008:5179:57e3:3c75:1886ov97hp":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":0,"longitude":0},"ASN":{}}}`,
		},
	}
	// TODO - make a test utility in geolite2 package.
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City", RegionCode: "ME",
			},
		},
	}
	for _, test := range tests {
		w := httptest.NewRecorder()
		r := httptest.NewRequest("POST", "/batch_annotate", strings.NewReader(test.body))
		handler.BatchAnnotate(w, r)
		body := w.Body.String()
		if string(body) != test.res && string(body) != test.alt {
			t.Errorf("\nGot\n__%s__\nexpected\n__%s__\n", body, test.res)
		}
	}
}

// TODO(JM) Update the test code/data here once we are no longer
// returning a canned response
func TestGetMetadataForSingleIP(t *testing.T) {
	tests := []struct {
		req *api.RequestData
		res api.GeoData
	}{
		{
			req: &api.RequestData{IP: "127.0.0.1", IPFormat: 4, Timestamp: time.Unix(0, 0)},
			res: api.GeoData{
				Geo: &api.GeolocationIP{City: "Not A Real City", PostalCode: "10583"},
				ASN: &api.IPASNData{}},
		},
	}
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City",
			},
		},
	}
	for _, test := range tests {
		res, _ := handler.GetMetadataForSingleIP(test.req)
		if !reflect.DeepEqual(res, test.res) {
			t.Errorf("Expected %v, got %v", test.res, res)
		}
	}
}
<commit_msg>Use deep.Equal<commit_after>package handler_test

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/go-test/deep"
	"github.com/m-lab/annotation-service/api"
	"github.com/m-lab/annotation-service/geolite2"
	"github.com/m-lab/annotation-service/handler"
	"github.com/m-lab/annotation-service/manager"
)

func init() {
	// Always prepend the filename and line number.
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func TestAnnotate(t *testing.T) {
	tests := []struct {
		ip   string
		time string
		res  string
	}{
		{"1.4.128.0", "625600", `{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":42.1,"longitude":-73.1},"ASN":{}}`},
		{"This will be an error.", "1000", "Invalid request"},
	}
	// TODO - make and use an annotator generator
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
				Latitude:      42.1,
				Longitude:     -73.1,
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
				Latitude:      42.1,
				Longitude:     -73.1,
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City", RegionCode: "ME",
			},
		},
	}
	for _, test := range tests {
		w := httptest.NewRecorder()
		r := &http.Request{}
		r.URL, _ = url.Parse("/annotate?ip_addr=" + url.QueryEscape(test.ip) + "&since_epoch=" + url.QueryEscape(test.time))
		handler.Annotate(w, r)
		body := w.Body.String()
		if string(body) != test.res {
			t.Errorf("\nGot\n__%s__\nexpected\n__%s__\n", body, test.res)
		}
	}
}

func TestValidateAndParse(t *testing.T) {
	tests := []struct {
		req *http.Request
		res *api.RequestData
		err error
	}{
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=127.0.0.1&since_epoch=fail", nil),
			res: nil,
			err: errors.New("invalid time"),
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=fail&since_epoch=10", nil),
			res: nil,
			err: errors.New("invalid IP address"),
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=127.0.0.1&since_epoch=10", nil),
			res: &api.RequestData{IP: "127.0.0.1", IPFormat: 4, Timestamp: time.Unix(10, 0)},
			err: nil,
		},
		{
			req: httptest.NewRequest("GET",
				"http://example.com/annotate?ip_addr=2620:0:1003:1008:5179:57e3:3c75:1886&since_epoch=10", nil),
			res: &api.RequestData{IP: "2620:0:1003:1008:5179:57e3:3c75:1886", IPFormat: 6, Timestamp: time.Unix(10, 0)},
			err: nil,
		},
	}
	for _, test := range tests {
		res, err := handler.ValidateAndParse(test.req)
		if diff := deep.Equal(res, test.res); diff != nil {
			t.Error(diff)
		}
		if diff := deep.Equal(err, test.err); diff != nil {
			t.Error(diff)
		}
	}

}

type badReader int

func (badReader) Read(_ []byte) (n int, err error) {
	return 0, errors.New("Bad Reader")
}

func TestBatchValidateAndParse(t *testing.T) {
	timeCon, _ := time.Parse(time.RFC3339, "2002-10-02T15:00:00Z")
	tests := []struct {
		source io.Reader
		res    []api.RequestData
		err    error
	}{
		{
			source: badReader(0),
			res:    nil,
			err:    errors.New("Bad Reader"),
		},
		{
			source: bytes.NewBufferString(`{`),
			res:    nil,
			err:    errors.New("unexpected end of JSON input"),
		},
		{
			source: bytes.NewBufferString(`[]`),
			res:    []api.RequestData{},
			err:    nil,
		},
		{
			source: bytes.NewBufferString(`[{"ip": "Bad IP", "timestamp": "2002-10-02T15:00:00Z"}]`),
			res:    nil,
			err:    errors.New("invalid IP address"),
		},
		{
			source: bytes.NewBufferString(`[{"ip": "127.0.0.1", "timestamp": "2002-10-02T15:00:00Z"},` +
				`{"ip": "2620:0:1003:1008:5179:57e3:3c75:1886", "timestamp": "2002-10-02T15:00:00Z"}]`),
			res: []api.RequestData{
				{IP: "127.0.0.1", IPFormat: 4, Timestamp: timeCon},
				{IP: "2620:0:1003:1008:5179:57e3:3c75:1886", IPFormat: 6, Timestamp: timeCon},
			},
			err: nil,
		},
	}
	for i, test := range tests {
		jsonBuffer, err := ioutil.ReadAll(test.source)
		if err != nil {
			if err.Error() != test.err.Error() {
				log.Printf("Expected %T\n", test.err)
				t.Error(err)
			}
			continue
		}
		res, err := handler.BatchValidateAndParse(jsonBuffer)
		if diff := deep.Equal(res, test.res); diff != nil {
			t.Error(diff)
		}
		// TODO use deep.Equal for testing errors?
		if err != nil && test.err == nil || err == nil && test.err != nil {
			t.Errorf("Test %d: Expected %+v, got %+v.", i, test.err, err)
			continue
		}
		if err != nil && test.err != nil && err.Error() != test.err.Error() {
			t.Errorf("Test %d: Expected %+v, got %+v.", i, test.err, err)
			continue
		}
	}

}

func TestBatchAnnotate(t *testing.T) {
	tests := []struct {
		body string
		res  string
		alt  string // Alternate valid result
	}{
		{
			body: "{",
			res:  "Invalid Request!",
			alt:  "",
		},
		{
			body: `[{"ip": "127.0.0.1", "timestamp": "2017-08-25T13:31:12.149678161-04:00"},
                               {"ip": "2620:0:1003:1008:5179:57e3:3c75:1886", "timestamp": "2017-08-25T14:32:13.149678161-04:00"}]`,
			res: `{"127.0.0.1ov94o0":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583"},"ASN":{}},"2620:0:1003:1008:5179:57e3:3c75:1886ov97hp":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583"},"ASN":{}}}`,
			// TODO - remove alt after updating json annotations to omitempty.
			alt: `{"127.0.0.1ov94o0":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":0,"longitude":0},"ASN":{}},"2620:0:1003:1008:5179:57e3:3c75:1886ov97hp":{"Geo":{"region":"ME","city":"Not A Real City","postal_code":"10583","latitude":0,"longitude":0},"ASN":{}}}`,
		},
	}
	// TODO - make a test utility in geolite2 package.
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City", RegionCode: "ME",
			},
		},
	}
	for _, test := range tests {
		w := httptest.NewRecorder()
		r := httptest.NewRequest("POST", "/batch_annotate", strings.NewReader(test.body))
		handler.BatchAnnotate(w, r)
		body := w.Body.String()
		if string(body) != test.res && string(body) != test.alt {
			t.Errorf("\nGot\n__%s__\nexpected\n__%s__\n", body, test.res)
		}
	}
}

// TODO(JM) Update the test code/data here once we are no longer
// returning a canned response
func TestGetMetadataForSingleIP(t *testing.T) {
	tests := []struct {
		req *api.RequestData
		res api.GeoData
	}{
		{
			req: &api.RequestData{IP: "127.0.0.1", IPFormat: 4, Timestamp: time.Unix(0, 0)},
			res: api.GeoData{
				Geo: &api.GeolocationIP{City: "Not A Real City", PostalCode: "10583"},
				ASN: &api.IPASNData{}},
		},
	}
	manager.CurrentAnnotator = &geolite2.GeoDataset{
		IP4Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IPv4(0, 0, 0, 0),
				IPAddressHigh: net.IPv4(255, 255, 255, 255),
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		IP6Nodes: []geolite2.IPNode{
			{
				IPAddressLow:  net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
				IPAddressHigh: net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
				LocationIndex: 0,
				PostalCode:    "10583",
			},
		},
		LocationNodes: []geolite2.LocationNode{
			{
				CityName: "Not A Real City",
			},
		},
	}
	for _, test := range tests {
		res, _ := handler.GetMetadataForSingleIP(test.req)
		if diff := deep.Equal(res, test.res); diff != nil {
			t.Error(diff)
		}
	}
}
<|endoftext|>