<commit_before>// Package aws provides AWS implementation of data interfaces.
//
// AWS Go SDK: https://github.com/aws/aws-sdk-go
// Go SDK API Ref: https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/
// Dev Guide: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Modifying.html
// REST API Ref: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html
package aws

import (
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"github.com/neptulon/shortid"
	"github.com/titan-x/titan/data"
	"github.com/titan-x/titan/models"
)

// DynamoDB implementation for DB interface.
type DynamoDB struct {
	DB      *dynamodb.DynamoDB
	Tables  []string
	Config  *aws.Config
	Session *session.Session
}

// NewDynamoDB creates a new AWS DynamoDB instance.
// region = Optional region setting. Will overwrite AWS_REGION env var if available.
// endpoint = Optional endpoint URL setting. Useful for specifying local/development service URL.
func NewDynamoDB(region string, endpoint string) *DynamoDB {
	db := DynamoDB{}
	db.Tables = []string{"users"}

	// carefully crafting config elements not to mess with the defaults
	if region != "" || endpoint != "" {
		db.Config = aws.NewConfig()

		if region != "" {
			db.Config.WithRegion(region)
		}
		if endpoint != "" {
			db.Config.WithEndpoint(endpoint)
		}

		db.Session = session.New(db.Config)
	} else {
		db.Session = session.New()
	}

	db.DB = dynamodb.New(db.Session)

	cred, err := db.DB.Config.Credentials.Get()
	if err != nil {
		log.Print("dynamodb: failed to initialize")
	} else {
		log.Printf("dynamodb: initialized with region: %v, access key ID: %v, endpoint: %v", *(db.DB.Config.Region), cred.AccessKeyID, db.DB.Config.Endpoint)
	}

	return &db
}

func (db *DynamoDB) listTables() ([]string, error) {
	res, err := db.DB.ListTables(&dynamodb.ListTablesInput{Limit: aws.Int64(100)})
	if err != nil {
		return nil, err
	}

	names := []string{}
	for _, name := range res.TableNames {
		names = append(names, *name)
	}

	return names, nil
}

func (db *DynamoDB) deleteTables() error {
	tables, err := db.listTables()
	if err != nil {
		return err
	}

	for _, tbl := range tables {
		if _, err := db.DB.DeleteTable(&dynamodb.DeleteTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	for _, tbl := range tables {
		if err := db.DB.WaitUntilTableNotExists(&dynamodb.DescribeTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	return nil
}

// Seed creates and populates the database, overwriting existing data if specified.
func (db *DynamoDB) Seed(overwrite bool, jwtPass string) error {
	if !overwrite {
		if tbls, err := db.listTables(); err != nil {
			return err
		} else if len(tbls) != 0 {
			return nil
		}
	}

	if err := db.deleteTables(); err != nil {
		return err
	}

	// create the tables
	for _, tbl := range db.Tables {
		tableParams := &dynamodb.CreateTableInput{
			TableName: aws.String(tbl),
			ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
				ReadCapacityUnits:  aws.Int64(1),
				WriteCapacityUnits: aws.Int64(1),
			},
			AttributeDefinitions: []*dynamodb.AttributeDefinition{
				{
					AttributeName: aws.String("ID"),
					AttributeType: aws.String("S"),
				},
				{
					AttributeName: aws.String("Email"),
					AttributeType: aws.String("S"),
				},
			},
			KeySchema: []*dynamodb.KeySchemaElement{
				{
					AttributeName: aws.String("ID"),
					KeyType:       aws.String("HASH"),
				},
			},
			GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
				{
					IndexName: aws.String("Email"),
					KeySchema: []*dynamodb.KeySchemaElement{
						{
							AttributeName: aws.String("Email"),
							KeyType:       aws.String("HASH"),
						},
					},
					Projection: &dynamodb.Projection{
						NonKeyAttributes: []*string{
							aws.String("Email"),
						},
						ProjectionType: aws.String("INCLUDE"),
					},
					ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
						ReadCapacityUnits:  aws.Int64(1),
						WriteCapacityUnits: aws.Int64(1),
					},
				},
			},
			// LocalSecondaryIndexes: []*dynamodb.LocalSecondaryIndex{
			// 	{
			// 		IndexName: aws.String("IndexName"),
			// 		KeySchema: []*dynamodb.KeySchemaElement{
			// 			{
			// 				AttributeName: aws.String("KeySchemaAttributeName"),
			// 				KeyType:       aws.String("KeyType"),
			// 			},
			//
			// 		},
			// 		Projection: &dynamodb.Projection{
			// 			NonKeyAttributes: []*string{
			// 				aws.String("NonKeyAttributeName"),
			//
			// 			},
			// 			ProjectionType: aws.String("ProjectionType"),
			// 		},
			// 	},
			// },
			// StreamSpecification: &dynamodb.StreamSpecification{
			// 	StreamEnabled:  aws.Bool(true),
			// 	StreamViewType: aws.String("StreamViewType"),
			// },
		}

		if _, err := db.DB.CreateTable(tableParams); err != nil {
			return err
		}

		// tables with secondary indexes need to be created sequentially so wait till table is ready
		if err := db.DB.WaitUntilTableExists(&dynamodb.DescribeTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	// insert the seed data
	if err := data.SeedInit(jwtPass); err != nil {
		return err
	}

	for _, u := range data.SeedUsers {
		if err := db.SaveUser(&u); err != nil {
			return err
		}
	}

	return nil
}

// GetByID retrieves a user by ID with OK indicator.
func (db *DynamoDB) GetByID(id string) (u *models.User, ok bool) {
	res, err := db.DB.GetItem(&dynamodb.GetItemInput{
		ConsistentRead: aws.Bool(true),
		TableName:      aws.String("users"),
		Key: map[string]*dynamodb.AttributeValue{
			"ID": {
				S: aws.String(id),
			},
		},
	})
	if err != nil {
		log.Printf("dynamodb: error: %v", err)
		return nil, false
	}
	if len(res.Item) == 0 {
		return nil, false
	}

	var user models.User
	if err := dynamodbattribute.UnmarshalMap(res.Item, &user); err != nil {
		log.Printf("dynamodb: getbyid error: %v", err)
		return nil, false
	}

	return &user, true
}

// GetByEmail retrieves a user by e-mail with OK indicator.
func (db *DynamoDB) GetByEmail(email string) (u *models.User, ok bool) {
	res, err := db.DB.Query(&dynamodb.QueryInput{
		// ConsistentRead: aws.Bool(true),
		TableName:              aws.String("users"),
		IndexName:              aws.String("Email"),
		Select:                 aws.String("ALL_ATTRIBUTES"),
		KeyConditionExpression: aws.String("Email = :Email"),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":Email": {
				S: aws.String(email),
			},
		},
	})
	if err != nil {
		log.Printf("dynamodb: getbymail error: %v", err)
		return nil, false
	}

	var user models.User
	if err := dynamodbattribute.UnmarshalMap(res.Items[0], &user); err != nil {
		log.Printf("dynamodb: getbyid error: %v", err)
		return nil, false
	}

	return &user, true
}

// SaveUser creates or updates a user. Upon creation, users are assigned a unique ID.
func (db *DynamoDB) SaveUser(u *models.User) error {
	if u.ID == "" {
		id, err := shortid.ID(64)
		if err != nil {
			return err
		}

		u.ID = id
	}

	item, err := dynamodbattribute.MarshalMap(u)
	if err != nil {
		return err
	}

	_, err = db.DB.PutItem(&dynamodb.PutItemInput{
		TableName: aws.String("users"),
		Item:      item,
	})
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>aws: dyn: print credentials during seed<commit_after>// Package aws provides AWS implementation of data interfaces.
//
// AWS Go SDK: https://github.com/aws/aws-sdk-go
// Go SDK API Ref: https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/
// Dev Guide: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Modifying.html
// REST API Ref: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html
package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"github.com/neptulon/shortid"
	"github.com/titan-x/titan/data"
	"github.com/titan-x/titan/models"
)

// DynamoDB implementation for DB interface.
type DynamoDB struct {
	DB      *dynamodb.DynamoDB
	Tables  []string
	Config  *aws.Config
	Session *session.Session
}

// NewDynamoDB creates a new AWS DynamoDB instance.
// region = Optional region setting. Will overwrite AWS_REGION env var if available.
// endpoint = Optional endpoint URL setting. Useful for specifying local/development service URL.
func NewDynamoDB(region string, endpoint string) *DynamoDB {
	db := DynamoDB{}
	db.Tables = []string{"users"}

	// carefully crafting config elements not to mess with the defaults
	if region != "" || endpoint != "" {
		db.Config = aws.NewConfig()

		if region != "" {
			db.Config.WithRegion(region)
		}
		if endpoint != "" {
			db.Config.WithEndpoint(endpoint)
		}

		db.Session = session.New(db.Config)
	} else {
		db.Session = session.New()
	}

	db.DB = dynamodb.New(db.Session)
	return &db
}

func (db *DynamoDB) listTables() ([]string, error) {
	res, err := db.DB.ListTables(&dynamodb.ListTablesInput{Limit: aws.Int64(100)})
	if err != nil {
		return nil, err
	}

	names := []string{}
	for _, name := range res.TableNames {
		names = append(names, *name)
	}

	return names, nil
}

func (db *DynamoDB) deleteTables() error {
	tables, err := db.listTables()
	if err != nil {
		return err
	}

	for _, tbl := range tables {
		if _, err := db.DB.DeleteTable(&dynamodb.DeleteTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	for _, tbl := range tables {
		if err := db.DB.WaitUntilTableNotExists(&dynamodb.DescribeTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	return nil
}

// Seed creates and populates the database, overwriting existing data if specified.
func (db *DynamoDB) Seed(overwrite bool, jwtPass string) error {
	cred, err := db.DB.Config.Credentials.Get()
	if err != nil {
		return fmt.Errorf("dynamodb: failed to initialize: %v", err)
	}

	log.Printf("dynamodb: initialized with region: %v, access key ID: %v, endpoint: %v", *(db.DB.Config.Region), cred.AccessKeyID, db.DB.Config.Endpoint)

	if !overwrite {
		if tbls, err := db.listTables(); err != nil {
			return err
		} else if len(tbls) != 0 {
			return nil
		}
	}

	if err := db.deleteTables(); err != nil {
		return err
	}

	// create the tables
	for _, tbl := range db.Tables {
		tableParams := &dynamodb.CreateTableInput{
			TableName: aws.String(tbl),
			ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
				ReadCapacityUnits:  aws.Int64(1),
				WriteCapacityUnits: aws.Int64(1),
			},
			AttributeDefinitions: []*dynamodb.AttributeDefinition{
				{
					AttributeName: aws.String("ID"),
					AttributeType: aws.String("S"),
				},
				{
					AttributeName: aws.String("Email"),
					AttributeType: aws.String("S"),
				},
			},
			KeySchema: []*dynamodb.KeySchemaElement{
				{
					AttributeName: aws.String("ID"),
					KeyType:       aws.String("HASH"),
				},
			},
			GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
				{
					IndexName: aws.String("Email"),
					KeySchema: []*dynamodb.KeySchemaElement{
						{
							AttributeName: aws.String("Email"),
							KeyType:       aws.String("HASH"),
						},
					},
					Projection: &dynamodb.Projection{
						NonKeyAttributes: []*string{
							aws.String("Email"),
						},
						ProjectionType: aws.String("INCLUDE"),
					},
					ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
						ReadCapacityUnits:  aws.Int64(1),
						WriteCapacityUnits: aws.Int64(1),
					},
				},
			},
			// LocalSecondaryIndexes: []*dynamodb.LocalSecondaryIndex{
			// 	{
			// 		IndexName: aws.String("IndexName"),
			// 		KeySchema: []*dynamodb.KeySchemaElement{
			// 			{
			// 				AttributeName: aws.String("KeySchemaAttributeName"),
			// 				KeyType:       aws.String("KeyType"),
			// 			},
			//
			// 		},
			// 		Projection: &dynamodb.Projection{
			// 			NonKeyAttributes: []*string{
			// 				aws.String("NonKeyAttributeName"),
			//
			// 			},
			// 			ProjectionType: aws.String("ProjectionType"),
			// 		},
			// 	},
			// },
			// StreamSpecification: &dynamodb.StreamSpecification{
			// 	StreamEnabled:  aws.Bool(true),
			// 	StreamViewType: aws.String("StreamViewType"),
			// },
		}

		if _, err := db.DB.CreateTable(tableParams); err != nil {
			return err
		}

		// tables with secondary indexes need to be created sequentially so wait till table is ready
		if err := db.DB.WaitUntilTableExists(&dynamodb.DescribeTableInput{TableName: aws.String(tbl)}); err != nil {
			return err
		}
	}

	// insert the seed data
	if err := data.SeedInit(jwtPass); err != nil {
		return err
	}

	for _, u := range data.SeedUsers {
		if err := db.SaveUser(&u); err != nil {
			return err
		}
	}

	return nil
}

// GetByID retrieves a user by ID with OK indicator.
func (db *DynamoDB) GetByID(id string) (u *models.User, ok bool) {
	res, err := db.DB.GetItem(&dynamodb.GetItemInput{
		ConsistentRead: aws.Bool(true),
		TableName:      aws.String("users"),
		Key: map[string]*dynamodb.AttributeValue{
			"ID": {
				S: aws.String(id),
			},
		},
	})
	if err != nil {
		log.Printf("dynamodb: error: %v", err)
		return nil, false
	}
	if len(res.Item) == 0 {
		return nil, false
	}

	var user models.User
	if err := dynamodbattribute.UnmarshalMap(res.Item, &user); err != nil {
		log.Printf("dynamodb: getbyid error: %v", err)
		return nil, false
	}

	return &user, true
}

// GetByEmail retrieves a user by e-mail with OK indicator.
func (db *DynamoDB) GetByEmail(email string) (u *models.User, ok bool) {
	res, err := db.DB.Query(&dynamodb.QueryInput{
		// ConsistentRead: aws.Bool(true),
		TableName:              aws.String("users"),
		IndexName:              aws.String("Email"),
		Select:                 aws.String("ALL_ATTRIBUTES"),
		KeyConditionExpression: aws.String("Email = :Email"),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":Email": {
				S: aws.String(email),
			},
		},
	})
	if err != nil {
		log.Printf("dynamodb: getbymail error: %v", err)
		return nil, false
	}

	var user models.User
	if err := dynamodbattribute.UnmarshalMap(res.Items[0], &user); err != nil {
		log.Printf("dynamodb: getbyid error: %v", err)
		return nil, false
	}

	return &user, true
}

// SaveUser creates or updates a user. Upon creation, users are assigned a unique ID.
func (db *DynamoDB) SaveUser(u *models.User) error {
	if u.ID == "" {
		id, err := shortid.ID(64)
		if err != nil {
			return err
		}

		u.ID = id
	}

	item, err := dynamodbattribute.MarshalMap(u)
	if err != nil {
		return err
	}

	_, err = db.DB.PutItem(&dynamodb.PutItemInput{
		TableName: aws.String("users"),
		Item:      item,
	})
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package mackerel

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

func TestListChannels(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/channels" {
			t.Error("request URL should be /api/v0/channels but: ", req.URL.Path)
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but: ", req.Method)
		}

		respJSON, _ := json.Marshal(map[string][]map[string]interface{}{
			"channels": {
				{
					"id":      "abcdefabc",
					"name":    "email channel",
					"type":    "email",
					"emails":  []string{"test@example.com", "test2@example.com"},
					"userIds": []string{"1234", "2345"},
					"events":  []string{"alert"},
				},
				{
					"id":   "bcdefabcd",
					"name": "slack channel",
					"type": "slack",
					"url":  "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX",
					"mentions": map[string]interface{}{
						"ok":      "ok message",
						"warning": "warning message",
					},
					"enabledGraphImage": true,
					"events":            []string{"alert"},
				},
				{
					"id":     "cdefabcde",
					"name":   "webhook channel",
					"type":   "webhook",
					"url":    "http://example.com/webhook",
					"events": []string{"alert"},
				},
				{
					"id":   "defabcdef",
					"name": "line channel",
					"type": "line",
				},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	channels, err := client.ListChannels()

	if err != nil {
		t.Error("err should be nil but: ", err)
	}

	if reflect.DeepEqual(channels[0].Emails, []string{"test@example.com", "test2@example.com"}) != true {
		t.Errorf("Wrong data for emails: %v", channels[0].Emails)
	}
	if reflect.DeepEqual(channels[0].UserIDs, []string{"1234", "2345"}) != true {
		t.Errorf("Wrong data for emails: %v", channels[0].UserIDs)
	}

	if channels[1].Mentions.OK != "ok message" {
		t.Error("request has mentions.ok but: ", channels[1].Mentions.OK)
	}
	if channels[1].Mentions.Warning != "warning message" {
		t.Error("request has mentions.warning but: ", channels[1].Mentions.Warning)
	}
	if channels[1].Mentions.Critical != "" {
		t.Error("request does not have mentions.critical but: ", channels[1].Mentions.Critical)
	}

	if channels[0].URL != "" {
		t.Error("request has no URL but: ", channels[0])
	}
	if channels[1].URL != "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX" {
		t.Error("request sends json including URL but: ", channels[1])
	}
	if channels[2].URL != "http://example.com/webhook" {
		t.Error("request sends json including URL but: ", channels[2])
	}
	if channels[3].URL != "" {
		t.Error("request has no URL but: ", channels[3])
	}
}
<commit_msg>CreateChannel wip<commit_after>package mackerel

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

func TestListChannels(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/channels" {
			t.Error("request URL should be /api/v0/channels but: ", req.URL.Path)
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but: ", req.Method)
		}

		respJSON, _ := json.Marshal(map[string][]map[string]interface{}{
			"channels": {
				{
					"id":      "abcdefabc",
					"name":    "email channel",
					"type":    "email",
					"emails":  []string{"test@example.com", "test2@example.com"},
					"userIds": []string{"1234", "2345"},
					"events":  []string{"alert"},
				},
				{
					"id":   "bcdefabcd",
					"name": "slack channel",
					"type": "slack",
					"url":  "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX",
					"mentions": map[string]interface{}{
						"ok":      "ok message",
						"warning": "warning message",
					},
					"enabledGraphImage": true,
					"events":            []string{"alert"},
				},
				{
					"id":     "cdefabcde",
					"name":   "webhook channel",
					"type":   "webhook",
					"url":    "http://example.com/webhook",
					"events": []string{"alert"},
				},
				{
					"id":   "defabcdef",
					"name": "line channel",
					"type": "line",
				},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	channels, err := client.ListChannels()

	if err != nil {
		t.Error("err should be nil but: ", err)
	}

	if reflect.DeepEqual(channels[0].Emails, []string{"test@example.com", "test2@example.com"}) != true {
		t.Errorf("Wrong data for emails: %v", channels[0].Emails)
	}
	if reflect.DeepEqual(channels[0].UserIDs, []string{"1234", "2345"}) != true {
		t.Errorf("Wrong data for emails: %v", channels[0].UserIDs)
	}

	if channels[1].Mentions.OK != "ok message" {
		t.Error("request has mentions.ok but: ", channels[1].Mentions.OK)
	}
	if channels[1].Mentions.Warning != "warning message" {
		t.Error("request has mentions.warning but: ", channels[1].Mentions.Warning)
	}
	if channels[1].Mentions.Critical != "" {
		t.Error("request does not have mentions.critical but: ", channels[1].Mentions.Critical)
	}

	if channels[0].URL != "" {
		t.Error("request has no URL but: ", channels[0])
	}
	if channels[1].URL != "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX" {
		t.Error("request sends json including URL but: ", channels[1])
	}
	if channels[2].URL != "http://example.com/webhook" {
		t.Error("request sends json including URL but: ", channels[2])
	}
	if channels[3].URL != "" {
		t.Error("request has no URL but: ", channels[3])
	}
}

func TestCreateChannel(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/channels" {
			t.Error("request URL should be /api/v0/channels but: ", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be POST but: ", req.Method)
		}

		respJSON, _ := json.Marshal(map[string]interface{}{
			"name": "slack channel",
			"type": "slack",
			"url":  "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX",
			"mentions": map[string]interface{}{
				"ok":      "ok message",
				"warning": "warning message",
			},
			"enabledGraphImage": true,
			"events":            []string{"alert"},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)

	_, err := client.CreateChannel(&ChannelWithoutID{
		Name: "slack channel",
		Type: "slack",
		URL:  "https://hooks.slack.com/services/TAAAA/BBBB/XXXXX",
		Mentions: Mentions{
			OK:      "ok message",
			Warning: "warning message",
		},
		EnabledGraphImage: true,
		Events:            []string{"alert"},
	})

	if err != nil {
		t.Error("err should be nil but: ", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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 chrometracing writes per-process Chrome trace_event files that can be
// loaded into chrome://tracing.
package chrometracing

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/google/chrometracing/traceinternal"
)

var trace = struct {
	start time.Time
	pid   uint64

	fileMu sync.Mutex
	file   *os.File
}{
	pid: uint64(os.Getpid()),
}

var out = setup(false)

// Path returns the full path of the chrome://tracing trace_event file for
// display in log messages.
func Path() string { return out }

// EnableTracing turns on tracing, regardless of running in a test or
// not. Tracing is enabled by default if the CHROMETRACING_DIR environment
// variable is present and non-empty.
func EnableTracing() {
	trace.fileMu.Lock()
	alreadyEnabled := trace.file != nil
	trace.fileMu.Unlock()
	if alreadyEnabled {
		return
	}
	out = setup(true)
}

func setup(overrideEnable bool) string {
	inTest := os.Getenv("TEST_TMPDIR") != ""
	explicitlyEnabled := os.Getenv("CHROMETRACING_DIR") != ""
	enableTracing := inTest || explicitlyEnabled || overrideEnable
	if !enableTracing {
		return ""
	}

	var err error
	dir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")
	if dir == "" {
		dir = os.Getenv("CHROMETRACING_DIR")
	}
	if dir == "" {
		dir = os.TempDir()
	}
	fn := filepath.Join(dir, fmt.Sprintf("%s.%d.trace", filepath.Base(os.Args[0]), trace.pid))
	trace.file, err = os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0644)
	if err != nil {
		// Using the log package from func init results in an error message
		// being printed.
		fmt.Fprintf(os.Stderr, "continuing without tracing: %v\n", err)
		return ""
	}

	// We only ever open a JSON array. Ending the array is optional as per
	// go/trace_event so that not cleanly finished traces can still be read.
	trace.file.Write([]byte{'['})
	trace.start = time.Now()

	writeEvent(&traceinternal.ViewerEvent{
		Name:  "process_name",
		Phase: "M", // Metadata Event
		Pid:   trace.pid,
		Tid:   trace.pid,
		Arg: struct {
			Name string `json:"name"`
		}{
			Name: strings.Join(os.Args, " "),
		},
	})
	return fn
}

func writeEvent(ev *traceinternal.ViewerEvent) {
	b, err := json.Marshal(&ev)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
	trace.fileMu.Lock()
	defer trace.fileMu.Unlock()
	if _, err = trace.file.Write(b); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
	if _, err = trace.file.Write([]byte{',', '\n'}); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
}

const (
	begin = "B"
	end   = "E"
)

// A PendingEvent represents an ongoing unit of work. The begin trace event has
// already been written, and calling Done will write the end trace event.
type PendingEvent struct {
	name string
	tid  uint64
}

// Done writes the end trace event for this unit of work.
func (pe *PendingEvent) Done() {
	if pe == nil || pe.name == "" || trace.file == nil {
		return
	}
	writeEvent(&traceinternal.ViewerEvent{
		Name:  pe.name,
		Phase: end,
		Pid:   trace.pid,
		Tid:   pe.tid,
		Time:  float64(time.Since(trace.start).Microseconds()),
	})
	releaseTid(pe.tid)
}

// Event logs a unit of work. To instrument a Go function, use e.g.:
//
//	func calcPi() {
//	  defer chrometracing.Event("calculate pi").Done()
//	  // …
//	}
//
// For more finely-granular traces, use e.g.:
//
//	for _, cmd := range commands {
//	  ev := chrometracing.Event("initialize " + cmd.Name)
//	  cmd.Init()
//	  ev.Done()
//	}
func Event(name string) *PendingEvent {
	if trace.file == nil {
		return &PendingEvent{}
	}
	tid := tid()
	writeEvent(&traceinternal.ViewerEvent{
		Name:  name,
		Phase: begin,
		Pid:   trace.pid,
		Tid:   tid,
		Time:  float64(time.Since(trace.start).Microseconds()),
	})
	return &PendingEvent{
		name: name,
		tid:  tid,
	}
}

// tids is a chrome://tracing thread id pool. Go does not permit accessing the
// goroutine id, so we need to maintain our own identifier. The chrome://tracing
// file format requires a numeric thread id, so we just increment whenever we
// need a thread id, and reuse the ones no longer in use.
//
// In practice, parallelized sections of the code (many goroutines) end up using
// only as few thread ids as are concurrently in use, and the rest of the events
// mirror the code call stack nicely. See e.g. http://screen/7MPcAcvXQNUE3JZ
var tids struct {
	sync.Mutex

	// We allocate chrome://tracing thread ids based on the index of the
	// corresponding entry in the used slice.
	used []bool

	// next points to the earliest unused tid to consider for the next tid to
	// hand out. This is purely a performance optimization to avoid O(n) slice
	// iteration.
	next int
}

func tid() uint64 {
	tids.Lock()
	defer tids.Unlock()
	// re-use released tids if any
	for t := tids.next; t < len(tids.used); t++ {
		if !tids.used[t] {
			tids.used[t] = true
			tids.next = t + 1
			return uint64(t)
		}
	}
	// allocate a new tid
	t := len(tids.used)
	tids.used = append(tids.used, true)
	tids.next = t + 1
	return uint64(t)
}

func releaseTid(t uint64) {
	tids.Lock()
	defer tids.Unlock()
	tids.used[int(t)] = false
	if tids.next > int(t) {
		tids.next = int(t)
	}
}
<commit_msg>Re-add accidentally removed Flush() method<commit_after>// Copyright 2020 Google LLC
//
// 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 chrometracing writes per-process Chrome trace_event files that can be
// loaded into chrome://tracing.
package chrometracing

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/google/chrometracing/traceinternal"
)

var trace = struct {
	start time.Time
	pid   uint64

	fileMu sync.Mutex
	file   *os.File
}{
	pid: uint64(os.Getpid()),
}

var out = setup(false)

// Path returns the full path of the chrome://tracing trace_event file for
// display in log messages.
func Path() string { return out }

// EnableTracing turns on tracing, regardless of running in a test or
// not. Tracing is enabled by default if the CHROMETRACING_DIR environment
// variable is present and non-empty.
func EnableTracing() {
	trace.fileMu.Lock()
	alreadyEnabled := trace.file != nil
	trace.fileMu.Unlock()
	if alreadyEnabled {
		return
	}
	out = setup(true)
}

func setup(overrideEnable bool) string {
	inTest := os.Getenv("TEST_TMPDIR") != ""
	explicitlyEnabled := os.Getenv("CHROMETRACING_DIR") != ""
	enableTracing := inTest || explicitlyEnabled || overrideEnable
	if !enableTracing {
		return ""
	}

	var err error
	dir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")
	if dir == "" {
		dir = os.Getenv("CHROMETRACING_DIR")
	}
	if dir == "" {
		dir = os.TempDir()
	}
	fn := filepath.Join(dir, fmt.Sprintf("%s.%d.trace", filepath.Base(os.Args[0]), trace.pid))
	trace.file, err = os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0644)
	if err != nil {
		// Using the log package from func init results in an error message
		// being printed.
		fmt.Fprintf(os.Stderr, "continuing without tracing: %v\n", err)
		return ""
	}

	// We only ever open a JSON array. Ending the array is optional as per
	// go/trace_event so that not cleanly finished traces can still be read.
	trace.file.Write([]byte{'['})
	trace.start = time.Now()

	writeEvent(&traceinternal.ViewerEvent{
		Name:  "process_name",
		Phase: "M", // Metadata Event
		Pid:   trace.pid,
		Tid:   trace.pid,
		Arg: struct {
			Name string `json:"name"`
		}{
			Name: strings.Join(os.Args, " "),
		},
	})
	return fn
}

func writeEvent(ev *traceinternal.ViewerEvent) {
	b, err := json.Marshal(&ev)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
	trace.fileMu.Lock()
	defer trace.fileMu.Unlock()
	if _, err = trace.file.Write(b); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
	if _, err = trace.file.Write([]byte{',', '\n'}); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}
}

const (
	begin = "B"
	end   = "E"
)

// A PendingEvent represents an ongoing unit of work. The begin trace event has
// already been written, and calling Done will write the end trace event.
type PendingEvent struct {
	name string
	tid  uint64
}

// Done writes the end trace event for this unit of work.
func (pe *PendingEvent) Done() {
	if pe == nil || pe.name == "" || trace.file == nil {
		return
	}
	writeEvent(&traceinternal.ViewerEvent{
		Name:  pe.name,
		Phase: end,
		Pid:   trace.pid,
		Tid:   pe.tid,
		Time:  float64(time.Since(trace.start).Microseconds()),
	})
	releaseTid(pe.tid)
}

// Event logs a unit of work. To instrument a Go function, use e.g.:
//
//	func calcPi() {
//	  defer chrometracing.Event("calculate pi").Done()
//	  // …
//	}
//
// For more finely-granular traces, use e.g.:
//
//	for _, cmd := range commands {
//	  ev := chrometracing.Event("initialize " + cmd.Name)
//	  cmd.Init()
//	  ev.Done()
//	}
func Event(name string) *PendingEvent {
	if trace.file == nil {
		return &PendingEvent{}
	}
	tid := tid()
	writeEvent(&traceinternal.ViewerEvent{
		Name:  name,
		Phase: begin,
		Pid:   trace.pid,
		Tid:   tid,
		Time:  float64(time.Since(trace.start).Microseconds()),
	})
	return &PendingEvent{
		name: name,
		tid:  tid,
	}
}

// tids is a chrome://tracing thread id pool. Go does not permit accessing the
// goroutine id, so we need to maintain our own identifier. The chrome://tracing
// file format requires a numeric thread id, so we just increment whenever we
// need a thread id, and reuse the ones no longer in use.
//
// In practice, parallelized sections of the code (many goroutines) end up using
// only as few thread ids as are concurrently in use, and the rest of the events
// mirror the code call stack nicely. See e.g. http://screen/7MPcAcvXQNUE3JZ
var tids struct {
	sync.Mutex

	// We allocate chrome://tracing thread ids based on the index of the
	// corresponding entry in the used slice.
	used []bool

	// next points to the earliest unused tid to consider for the next tid to
	// hand out. This is purely a performance optimization to avoid O(n) slice
	// iteration.
	next int
}

func tid() uint64 {
	tids.Lock()
	defer tids.Unlock()
	// re-use released tids if any
	for t := tids.next; t < len(tids.used); t++ {
		if !tids.used[t] {
			tids.used[t] = true
			tids.next = t + 1
			return uint64(t)
		}
	}
	// allocate a new tid
	t := len(tids.used)
	tids.used = append(tids.used, true)
	tids.next = t + 1
	return uint64(t)
}

func releaseTid(t uint64) {
	tids.Lock()
	defer tids.Unlock()
	tids.used[int(t)] = false
	if tids.next > int(t) {
		tids.next = int(t)
	}
}

// Flush should be called before your program terminates, and/or periodically
// for long-running programs, to flush any pending chrome://tracing events out
// to disk.
func Flush() error {
	trace.fileMu.Lock()
	defer trace.fileMu.Unlock()
	if err := trace.file.Sync(); err != nil {
		return fmt.Errorf("flushing trace file: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package ui

import (
	"image"
	"image/draw"
	"log"
	"math"
	"time"

	"github.com/ninjasphere/go-gestic"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/logger"
)

const width = 16
const height = 16

const ignoreFirstGestureAfterDuration = time.Second
const panDuration = time.Millisecond * 350
const wakeTransitionDuration = time.Millisecond * 1
const sleepTimeout = time.Second * 20
const sleepTransitionDuration = time.Second * 5

type PaneLayout struct {
	currentPane int
	targetPane  int
	panes       []Pane
	lastGesture time.Time

	panTween *Tween

	awake     bool
	fadeTween *Tween
	wake      chan (bool)

	log *logger.Logger

	fps      *Tick
	gestures *Tick
}

func NewPaneLayout(fakeGestures bool, conn *ninja.Connection) (*PaneLayout, chan (bool)) {

	startSearchTasks(conn)

	pane := &PaneLayout{
		fps: &Tick{
			name: "Pane FPS",
		},
		gestures: &Tick{
			name: "Gestures/sec",
		},
		wake: make(chan bool),
		log:  logger.GetLogger("PaneLayout"),
	}
	pane.fps.start()
	pane.gestures.start()

	if !fakeGestures {
		gestic.ResetDevice()
		reader := gestic.NewReader(logger.GetLogger("Gestic"), pane.OnGesture)
		go reader.Start()
	}

	// Check for sleep timeout
	go func() {
		for {
			time.Sleep(time.Millisecond * 50)
			if pane.awake && time.Since(pane.lastGesture) > sleepTimeout {
				pane.Sleep()
			}
		}
	}()

	if fakeGestures {
		go func() {
			for {
				time.Sleep(time.Millisecond * 5000)
				gesture := gestic.NewGestureData()
				gesture.Gesture.GestureVal = 2
				pane.OnGesture(gesture)
			}
		}()

		go func() {
			for {
				time.Sleep(time.Millisecond * 10)
				gesture := gestic.NewGestureData()
				gesture.Coordinates.X = 100
				gesture.Coordinates.Y = 100
				gesture.Coordinates.Z = 100
				pane.OnGesture(gesture)
			}
		}()
	}

	return pane, pane.wake
}

type Pane interface {
	Render() (*image.RGBA, error)
	Gesture(*gestic.GestureData)
}

func (l *PaneLayout) Wake() {

	l.log.Infof("Waking up")

	currentFade := 0.0

	if l.fadeTween != nil {
		currentFade, _ = l.fadeTween.Update()
	}

	l.awake = true

	l.fadeTween = &Tween{
		From:     currentFade,
		To:       1,
		Start:    time.Now(),
		Duration: wakeTransitionDuration, // Alter duration if not starting at 0?
		Ease:     easeOutQuint,
	}
	l.wake <- true
}

func (l *PaneLayout) OnGesture(g *gestic.GestureData) {

	// Always skip the first gesture if we haven't had any for ignoreFirstGestureAfterDuration
	skip := false

	if time.Now().Sub(l.lastGesture) > ignoreFirstGestureAfterDuration {
		log.Printf("Ignoring first gesture")
		skip = true
	}

	l.gestures.tick()

	l.lastGesture = time.Now()

	if skip {
		return
	}

	//spew.Dump(g)

	// If we're asleep, wake up
	if !l.awake {
		l.Wake()
		return
	}

	// Ignore all gestures while we're fading in or out
	if l.fadeTween == nil {

		if g.Gesture.Name() == "EastToWest" {
			l.panBy(1)
			l.log.Infof("East to west, panning by 1")
		}

		if g.Gesture.Name() == "WestToEast" {
			l.panBy(-1)
			l.log.Infof("West to east, panning by -1")
		}

		if l.panTween == nil {
			go l.panes[l.currentPane].Gesture(g)
		}
	}
}

func (l *PaneLayout) Sleep() {
	l.log.Infof("Going to sleep")
	l.awake = false

	l.fadeTween = &Tween{
		From:     1,
		To:       0,
		Start:    time.Now(),
		Duration: sleepTransitionDuration,
	}
}

func (l *PaneLayout) AddPane(pane Pane) {
	l.panes = append(l.panes, pane)
}

func (l *PaneLayout) IsDirty() bool {
	return true
}

func (l *PaneLayout) Render() (*image.RGBA, chan (bool), error) {

	frame := image.NewRGBA(image.Rect(0, 0, width, height))

	l.fps.tick()

	if l.fadeTween != nil {
		_, done := l.fadeTween.Update()

		if done {
			l.fadeTween = nil
		}
	}

	if !l.awake && l.fadeTween == nil {
		log.Println("Sending blank frame and wake chan")
		return frame, l.wake, nil
	}

	var position = 0
	if l.panTween != nil {
		var done bool
		floatPosition, done := l.panTween.Update()
		position = int(math.Floor(floatPosition))
		if done {
			l.panTween = nil
			l.currentPane = l.targetPane
			position = 0
		}
	}

	if position != 0 {
		l.log.Infof("Rendering pane %d with pixel offset %d and panning to %d", l.currentPane, position, l.targetPane)
	}

	// Render the current image at the current position
	currentImage, err := l.panes[l.currentPane].Render()

	if err != nil {
		return nil, nil, err
	}

	draw.Draw(frame, frame.Bounds(), currentImage, image.Point{position, 0}, draw.Src)

	if position != 0 {
		// We have the target pane to draw too

		targetImage, err := l.panes[l.targetPane].Render()
		if err != nil {
			return nil, nil, err
		}

		var targetPosition int
		if position < 0 {
			// Panning right
			targetPosition = width + position
		} else {
			// Panning left
			targetPosition = position - width
		}

		draw.Draw(frame, frame.Bounds(), targetImage, image.Point{targetPosition, 0}, draw.Src)
	}

	if l.fadeTween != nil {
		// We're fading in or out...

		fade, _ := l.fadeTween.Update()
		if fade > 1 {
			fade = 1
		}

		for i := 0; i < len(frame.Pix); i = i + 4 {
			//log.Println(i)
			frame.Pix[i] = uint8(float64(frame.Pix[i]) * fade)
			frame.Pix[i+1] = uint8(float64(frame.Pix[i+1]) * fade)
			frame.Pix[i+2] = uint8(float64(frame.Pix[i+2]) * fade)
		}
	}

	return frame, nil, nil
}

func (l *PaneLayout) panBy(delta int) {
	l.currentPane = l.targetPane
	l.targetPane += delta
	if l.targetPane < 0 {
		l.targetPane = (len(l.panes) - 1)
	}
	if l.targetPane > (len(l.panes) - 1) {
		l.targetPane = 0
	}

	l.log.Infof("panning from pane %d to %d", l.currentPane, l.targetPane)

	l.panTween = &Tween{
		From:     0,
		Start:    time.Now(),
		Duration: panDuration,
	}

	if delta > 0 {
		l.panTween.To = width
	} else {
		l.panTween.To = -width
	}

}

type Tween struct {
	From     float64
	To       float64
	Ease     func(t float64, b float64, c float64, d float64) float64
	Start    time.Time
	Duration time.Duration
}

func (t *Tween) Update() (float64, bool) {
	position := float64(time.Now().Sub(t.Start)) / float64(t.Duration)

	if position > 1 {
		// we're done
		return float64(t.To), true
	}

	if t.Ease != nil {
		position = t.Ease(0.0, t.From, position-t.From, float64(t.Duration))
	}

	value := (float64(t.To-t.From) * position) + float64(t.From)

	return value, value == t.To
}

// from http://gizma.com/easing/
// t - start time
// b - start value
// c - change in value
// d - duration
func easeOutQuint(t float64, b float64, c float64, d float64) float64 {
	t /= d
	t--
	return c*(t*t*t*t*t+1) + b
}

type Tick struct {
	count int
	name  string
}

func (t *Tick) tick() {
	t.count++
}

func (t *Tick) start() {
	go func() {
		for {
			time.Sleep(time.Second)
			//log.Printf("%s - %d", t.name, t.count)
			t.count = 0
		}
	}()
}
<commit_msg>Use gastric Reader.MaybeStart()<commit_after>package ui

import (
	"image"
	"image/draw"
	"log"
	"math"
	"time"

	"github.com/ninjasphere/go-gestic"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/logger"
)

const width = 16
const height = 16

const ignoreFirstGestureAfterDuration = time.Second
const panDuration = time.Millisecond * 350
const wakeTransitionDuration = time.Millisecond * 1
const sleepTimeout = time.Second * 20
const sleepTransitionDuration = time.Second * 5

type PaneLayout struct {
	currentPane int
	targetPane  int
	panes       []Pane
	lastGesture time.Time

	panTween *Tween

	awake     bool
	fadeTween *Tween
	wake      chan (bool)

	log *logger.Logger

	fps      *Tick
	gestures *Tick
}

func NewPaneLayout(fakeGestures bool, conn *ninja.Connection) (*PaneLayout, chan (bool)) {

	startSearchTasks(conn)

	pane := &PaneLayout{
		fps: &Tick{
			name: "Pane FPS",
		},
		gestures: &Tick{
			name: "Gestures/sec",
		},
		wake: make(chan bool),
		log:  logger.GetLogger("PaneLayout"),
	}
	pane.fps.start()
	pane.gestures.start()

	if !fakeGestures {
		gestic.ResetDevice()
		reader := gestic.NewReader(logger.GetLogger("Gestic"), pane.OnGesture)
		go reader.MaybeStart()
	}

	// Check for sleep timeout
	go func() {
		for {
			time.Sleep(time.Millisecond * 50)
			if pane.awake && time.Since(pane.lastGesture) > sleepTimeout {
				pane.Sleep()
			}
		}
	}()

	if fakeGestures {
		go func() {
			for {
				time.Sleep(time.Millisecond * 5000)
				gesture := gestic.NewGestureData()
				gesture.Gesture.GestureVal = 2
				pane.OnGesture(gesture)
			}
		}()

		go func() {
			for {
				time.Sleep(time.Millisecond * 10)
				gesture := gestic.NewGestureData()
				gesture.Coordinates.X = 100
				gesture.Coordinates.Y = 100
				gesture.Coordinates.Z = 100
				pane.OnGesture(gesture)
			}
		}()
	}

	return pane, pane.wake
}

type Pane interface {
	Render() (*image.RGBA, error)
	Gesture(*gestic.GestureData)
}

func (l *PaneLayout) Wake() {

	l.log.Infof("Waking up")

	currentFade := 0.0

	if l.fadeTween != nil {
		currentFade, _ = l.fadeTween.Update()
	}

	l.awake = true

	l.fadeTween = &Tween{
		From:     currentFade,
		To:       1,
		Start:    time.Now(),
		Duration: wakeTransitionDuration, // Alter duration if not starting at 0?
		Ease:     easeOutQuint,
	}
	l.wake <- true
}

func (l *PaneLayout) OnGesture(g *gestic.GestureData) {

	// Always skip the first gesture if we haven't had any for ignoreFirstGestureAfterDuration
	skip := false

	if time.Now().Sub(l.lastGesture) > ignoreFirstGestureAfterDuration {
		log.Printf("Ignoring first gesture")
		skip = true
	}

	l.gestures.tick()

	l.lastGesture = time.Now()

	if skip {
		return
	}

	//spew.Dump(g)

	// If we're asleep, wake up
	if !l.awake {
		l.Wake()
		return
	}

	// Ignore all gestures while we're fading in or out
	if l.fadeTween == nil {

		if g.Gesture.Name() == "EastToWest" {
			l.panBy(1)
			l.log.Infof("East to west, panning by 1")
		}

		if g.Gesture.Name() == "WestToEast" {
			l.panBy(-1)
			l.log.Infof("West to east, panning by -1")
		}

		if l.panTween == nil {
			go l.panes[l.currentPane].Gesture(g)
		}
	}
}

func (l *PaneLayout) Sleep() {
	l.log.Infof("Going to sleep")
	l.awake = false

	l.fadeTween = &Tween{
		From:     1,
		To:       0,
		Start:    time.Now(),
		Duration: sleepTransitionDuration,
	}
}

func (l *PaneLayout) AddPane(pane Pane) {
	l.panes = append(l.panes, pane)
}

func (l *PaneLayout) IsDirty() bool {
	return true
}

func (l *PaneLayout) Render() (*image.RGBA, chan (bool), error) {

	frame := image.NewRGBA(image.Rect(0, 0, width, height))

	l.fps.tick()

	if l.fadeTween != nil {
		_, done := l.fadeTween.Update()

		if done {
			l.fadeTween = nil
		}
	}

	if !l.awake && l.fadeTween == nil {
		log.Println("Sending blank frame and wake chan")
		return frame, l.wake, nil
	}

	var position = 0
	if l.panTween != nil {
		var done bool
		floatPosition, done := l.panTween.Update()
		position = int(math.Floor(floatPosition))
		if done {
			l.panTween = nil
			l.currentPane = l.targetPane
			position = 0
		}
	}

	if position != 0 {
		l.log.Infof("Rendering pane %d with pixel offset %d and panning to %d", l.currentPane, position, l.targetPane)
	}

	// Render the current image at the current position
	currentImage, err := l.panes[l.currentPane].Render()

	if err != nil {
		return nil, nil, err
	}

	draw.Draw(frame, frame.Bounds(), currentImage, image.Point{position, 0}, draw.Src)

	if position != 0 {
		// We have the target pane to draw too

		targetImage, err := l.panes[l.targetPane].Render()
		if err != nil {
			return nil, nil, err
		}

		var targetPosition int
		if position < 0 {
			// Panning right
			targetPosition = width + position
		} else {
			// Panning left
			targetPosition = position - width
		}

		draw.Draw(frame, frame.Bounds(), targetImage, image.Point{targetPosition, 0}, draw.Src)
	}

	if l.fadeTween != nil {
		// We're fading in or out...

		fade, _ := l.fadeTween.Update()
		if fade > 1 {
			fade = 1
		}

		for i := 0; i < len(frame.Pix); i = i + 4 {
			//log.Println(i)
			frame.Pix[i] = uint8(float64(frame.Pix[i]) * fade)
			frame.Pix[i+1] = uint8(float64(frame.Pix[i+1]) * fade)
			frame.Pix[i+2] = uint8(float64(frame.Pix[i+2]) * fade)
		}
	}

	return frame, nil, nil
}

func (l *PaneLayout) panBy(delta int) {
	l.currentPane = l.targetPane
	l.targetPane += delta
	if l.targetPane < 0 {
		l.targetPane = (len(l.panes) - 1)
	}
	if l.targetPane > (len(l.panes) - 1) {
		l.targetPane = 0
	}

	l.log.Infof("panning from pane %d to %d", l.currentPane, l.targetPane)

	l.panTween = &Tween{
		From:     0,
		Start:    time.Now(),
		Duration: panDuration,
	}

	if delta > 0 {
		l.panTween.To = width
	} else {
		l.panTween.To = -width
	}

}

type Tween struct {
	From     float64
	To       float64
	Ease     func(t float64, b float64, c float64, d float64) float64
	Start    time.Time
	Duration time.Duration
}

func (t *Tween) Update() (float64, bool) {
	position := float64(time.Now().Sub(t.Start)) / float64(t.Duration)

	if position > 1 {
		// we're done
		return float64(t.To), true
	}

	if t.Ease != nil {
		position = t.Ease(0.0, t.From, position-t.From, float64(t.Duration))
	}

	value := (float64(t.To-t.From) * position) + float64(t.From)

	return value, value == t.To
}

// from http://gizma.com/easing/
// t - start time
// b - start value
// c - change in value
// d - duration
func easeOutQuint(t float64, b float64, c float64, d float64) float64 {
	t /= d
	t--
	return c*(t*t*t*t*t+1) + b
}

type Tick struct {
	count int
	name  string
}

func (t *Tick) tick() {
	t.count++
}

func (t *Tick) start() {
	go func() {
		for {
			time.Sleep(time.Second)
			//log.Printf("%s - %d", t.name, t.count)
			t.count = 0
		}
	}()
}
<|endoftext|>


<commit_before>package move

import (
	"errors"
	"net/http"
	"net/url"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/instance/lifecycle"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/model/oauth"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	multierror "github.com/hashicorp/go-multierror"
	jwt "gopkg.in/dgrijalva/jwt-go.v3"
)

const (
	// MoveScope is the scope requested for a move (when we don't know yet if
	// the cozy will be the source or the target).
	MoveScope = consts.ExportsRequests + " " + consts.Imports
	// SourceClientID is the fake OAuth client ID used for some move endpoints.
	SourceClientID = "move"
)

// Request is a struct for confirming a move to another Cozy.
type Request struct {
	SourceCreds RequestCredentials `json:"source_credentials"`
	TargetCreds RequestCredentials `json:"target_credentials"`
	Target      string             `json:"target"`
	Link        string             `json:"-"`
}

// RequestCredentials is struct for OAuth credentials (access_token, client_id
// and client_secret).
type RequestCredentials struct {
	Token        string `json:"token"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// TargetHost returns the host part of the target instance address.
func (r *Request) TargetHost() string {
	if u, err := url.Parse(r.Target); err == nil {
		return u.Host
	}
	return r.Target
}

// ImportingURL returns the URL on the target for the page to wait until
// the move is done.
func (r *Request) ImportingURL() string {
	u, err := url.Parse(r.Target)
	if err != nil {
		u, err = url.Parse("https://" + r.Target)
	}
	if err != nil {
		return r.Target
	}
	u.Path = "/move/importing"
	return u.String()
}

// CreateRequestClient creates an OAuth client that can be used for move requests.
func CreateRequestClient(inst *instance.Instance) (*oauth.Client, error) {
	client := &oauth.Client{
		RedirectURIs: []string{config.GetConfig().Move.URL + "/fake"},
		ClientName:   "cozy-stack",
		SoftwareID:   "github.com/cozy/cozy-stack",
	}
	if err := client.Create(inst); err != nil {
		return nil, errors.New(err.Error)
	}
	return client, nil
}

// CreateRequest checks if the parameters are OK for moving, and if yes, it
// will persist them and return a link that can be used to confirm the move.
func CreateRequest(inst *instance.Instance, params url.Values) (*Request, error) {
	var source RequestCredentials
	code := params.Get("code")
	if code == "" {
		source.Token = params.Get("token")
		if source.Token == "" {
			return nil, errors.New("No core or token")
		}
		if err := checkSourceToken(inst, source); err != nil {
			return nil, err
		}
		source.ClientID = params.Get("client_id")
		if source.ClientID == "" {
			return nil, errors.New("No client_id")
		}
		source.ClientSecret = params.Get("client_secret")
		if source.ClientSecret == "" {
			return nil, errors.New("No client_secret")
		}
	} else {
		if err := checkSourceCode(inst, code); err != nil {
			return nil, err
		}
		client, err := CreateRequestClient(inst)
		if err != nil {
			return nil, err
		}
		token, err := client.CreateJWT(inst, consts.AccessTokenAudience, MoveScope)
		if err != nil {
			return nil, err
		}
		source.ClientID = client.ClientID
		source.ClientSecret = client.ClientSecret
		source.Token = token
	}

	var target RequestCredentials
	cozyURL := params.Get("target_url")
	if cozyURL == "" {
		return nil, errors.New("No target_url")
	}
	if inst.HasDomain(cozyURL) {
		return nil, errors.New("Invalid target_url")
	}
	target.Token = params.Get("target_token")
	if target.Token == "" {
		return nil, errors.New("No target_token")
	}
	target.ClientID = params.Get("target_client_id")
	if target.ClientID == "" {
		return nil, errors.New("No target_client_id")
	}
	target.ClientSecret = params.Get("target_client_secret")
	if target.ClientSecret == "" {
		return nil, errors.New("No target_client_secret")
	}

	req := &Request{
		SourceCreds: source,
		TargetCreds: target,
		Target:      cozyURL,
	}

	secret, err := getStore().Save(inst, req)
	if err != nil {
		return nil, err
	}

	req.Link = inst.PageURL("/move/go", url.Values{"secret": {secret}})
	return req, nil
}

func checkSourceToken(inst *instance.Instance, source RequestCredentials) error {
	var claims permission.Claims
	err := crypto.ParseJWT(source.Token, func(token *jwt.Token) (interface{}, error) {
		return consts.AccessTokenAudience, nil
	}, &claims)
	if err != nil {
		return permission.ErrInvalidToken
	}

	if claims.Issuer != inst.Domain {
		return permission.ErrInvalidToken
	}
	if claims.Expired() {
		return permission.ErrExpiredToken
	}

	c, err := oauth.FindClient(inst, claims.Subject)
	if err != nil {
		if couchdb.IsInternalServerError(err) {
			return err
		}
		return permission.ErrInvalidToken
	}

	if c.ClientID != source.ClientID {
		return permission.ErrInvalidToken
	}
	if c.ClientSecret != source.ClientSecret {
		return permission.ErrInvalidToken
	}
	return nil
}

func checkSourceCode(inst *instance.Instance, code string) error {
	accessCode := &oauth.AccessCode{}
	if err := couchdb.GetDoc(inst, consts.OAuthAccessCodes, code, accessCode); err != nil {
		return permission.ErrInvalidToken
	}
	if accessCode.ClientID != SourceClientID {
		return permission.ErrInvalidToken
	}
	if accessCode.Scope != consts.ExportsRequests {
		return permission.ErrInvalidToken
	}
	return nil
}

// StartMove checks that the secret is known, sends a request to the other Cozy
// to block it during the move, and pushs a job for the export.
func StartMove(inst *instance.Instance, secret string) (*Request, error) {
	req, err := getStore().Get(inst, secret)
	if err != nil {
		return nil, err
	}

	u := req.ImportingURL() + "?source=" + inst.ContextualDomain()
	_, err = http.Post(u, "application/json", nil)
	if err != nil {
		return nil, errors.New("Cannot reach the other Cozy")
	}

	options := ExportOptions{
		ContextualDomain: inst.ContextualDomain(),
		TokenSource:      req.SourceCreds.Token,
		MoveTo: &MoveToOptions{
			URL:          req.Target,
			Token:        req.TargetCreds.Token,
			ClientID:     req.TargetCreds.ClientID,
			ClientSecret: req.TargetCreds.ClientSecret,
		},
	}
	msg, err := job.NewMessage(options)
	if err != nil {
		return nil, err
	}
	_, err = job.System().PushJob(inst, &job.JobRequest{
		WorkerType: "export",
		Message:    msg,
	})
	return req, err
}

// CallFinalize will call the /move/finalize endpoint on the other instance to
// unblock it after a successful move.
func CallFinalize(inst *instance.Instance, otherURL, token string) {
	u, err := url.Parse(otherURL)
	if err != nil {
		u, err = url.Parse("https://" + otherURL)
	}
	if err != nil {
		return
	}
	u.Path = "/move/finalize"
	req, err := http.NewRequest("POST", u.String(), nil)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: %s", err)
		return
	}
	req.Header.Add("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: %s", err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 204 {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: code=%d", res.StatusCode)
	}

	doc, err := inst.SettingsDocument()
	if err == nil {
		doc.M["moved_from"] = u.Host
		_ = couchdb.UpdateDoc(inst, doc)
	}
}

// Finalize makes the last steps on the source Cozy after the data has been
// successfully imported:
// - stop the konnectors
// - warn the OAuth clients
// - unblock the instance
func Finalize(inst *instance.Instance) error {
	var errm error
	sched := job.System()
	triggers, err := sched.GetAllTriggers(inst)
	if err == nil {
		for _, t := range triggers {
			infos := t.Infos()
			if infos.WorkerType == "konnector" {
				if err = sched.DeleteTrigger(inst, infos.TID); err != nil {
					errm = multierror.Append(errm, err)
				}
			}
		}
	} else {
		errm = multierror.Append(errm, err)
	}
	inst.Moved = true
	if err := lifecycle.Unblock(inst); err != nil {
		errm = multierror.Append(errm, err)
	}
	return errm
}

// Abort will call the /move/abort endpoint on the other instance to unblock it
// after a failed export or import during a move.
func Abort(inst *instance.Instance, otherURL, token string) {
	u, err := url.Parse(otherURL)
	if err != nil {
		u, err = url.Parse("https://" + otherURL)
	}
	if err != nil {
		return
	}
	u.Path = "/move/abort"
	req, err := http.NewRequest("POST", u.String(), nil)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: %s", err)
		return
	}
	req.Header.Add("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: %s", err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 204 {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: code=%d", res.StatusCode)
	}
}
<commit_msg>Fix token for /move/finalize<commit_after>package move

import (
	"errors"
	"net/http"
	"net/url"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/instance/lifecycle"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/model/oauth"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	multierror "github.com/hashicorp/go-multierror"
	jwt "gopkg.in/dgrijalva/jwt-go.v3"
)

const (
	// MoveScope is the scope requested for a move (when we don't know yet if
	// the cozy will be the source or the target).
	MoveScope = consts.ExportsRequests + " " + consts.Imports
	// SourceClientID is the fake OAuth client ID used for some move endpoints.
	SourceClientID = "move"
)

// Request is a struct for confirming a move to another Cozy.
type Request struct {
	SourceCreds RequestCredentials `json:"source_credentials"`
	TargetCreds RequestCredentials `json:"target_credentials"`
	Target      string             `json:"target"`
	Link        string             `json:"-"`
}

// RequestCredentials is struct for OAuth credentials (access_token, client_id
// and client_secret).
type RequestCredentials struct {
	Token        string `json:"token"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// TargetHost returns the host part of the target instance address.
func (r *Request) TargetHost() string {
	if u, err := url.Parse(r.Target); err == nil {
		return u.Host
	}
	return r.Target
}

// ImportingURL returns the URL on the target for the page to wait until
// the move is done.
func (r *Request) ImportingURL() string {
	u, err := url.Parse(r.Target)
	if err != nil {
		u, err = url.Parse("https://" + r.Target)
	}
	if err != nil {
		return r.Target
	}
	u.Path = "/move/importing"
	return u.String()
}

// CreateRequestClient creates an OAuth client that can be used for move requests.
func CreateRequestClient(inst *instance.Instance) (*oauth.Client, error) {
	client := &oauth.Client{
		RedirectURIs: []string{config.GetConfig().Move.URL + "/fake"},
		ClientName:   "cozy-stack",
		SoftwareID:   "github.com/cozy/cozy-stack",
	}
	if err := client.Create(inst); err != nil {
		return nil, errors.New(err.Error)
	}
	return client, nil
}

// CreateRequest checks if the parameters are OK for moving, and if yes, it
// will persist them and return a link that can be used to confirm the move.
func CreateRequest(inst *instance.Instance, params url.Values) (*Request, error) {
	var source RequestCredentials
	code := params.Get("code")
	if code == "" {
		source.Token = params.Get("token")
		if source.Token == "" {
			return nil, errors.New("No core or token")
		}
		if err := checkSourceToken(inst, source); err != nil {
			return nil, err
		}
		source.ClientID = params.Get("client_id")
		if source.ClientID == "" {
			return nil, errors.New("No client_id")
		}
		source.ClientSecret = params.Get("client_secret")
		if source.ClientSecret == "" {
			return nil, errors.New("No client_secret")
		}
	} else {
		if err := checkSourceCode(inst, code); err != nil {
			return nil, err
		}
		client, err := CreateRequestClient(inst)
		if err != nil {
			return nil, err
		}
		client.CouchID = client.ClientID
		token, err := client.CreateJWT(inst, consts.AccessTokenAudience, MoveScope)
		if err != nil {
			return nil, err
		}
		source.ClientID = client.ClientID
		source.ClientSecret = client.ClientSecret
		source.Token = token
	}

	var target RequestCredentials
	cozyURL := params.Get("target_url")
	if cozyURL == "" {
		return nil, errors.New("No target_url")
	}
	if inst.HasDomain(cozyURL) {
		return nil, errors.New("Invalid target_url")
	}
	target.Token = params.Get("target_token")
	if target.Token == "" {
		return nil, errors.New("No target_token")
	}
	target.ClientID = params.Get("target_client_id")
	if target.ClientID == "" {
		return nil, errors.New("No target_client_id")
	}
	target.ClientSecret = params.Get("target_client_secret")
	if target.ClientSecret == "" {
		return nil, errors.New("No target_client_secret")
	}

	req := &Request{
		SourceCreds: source,
		TargetCreds: target,
		Target:      cozyURL,
	}

	secret, err := getStore().Save(inst, req)
	if err != nil {
		return nil, err
	}

	req.Link = inst.PageURL("/move/go", url.Values{"secret": {secret}})
	return req, nil
}

func checkSourceToken(inst *instance.Instance, source RequestCredentials) error {
	var claims permission.Claims
	err := crypto.ParseJWT(source.Token, func(token *jwt.Token) (interface{}, error) {
		return consts.AccessTokenAudience, nil
	}, &claims)
	if err != nil {
		return permission.ErrInvalidToken
	}

	if claims.Issuer != inst.Domain {
		return permission.ErrInvalidToken
	}
	if claims.Expired() {
		return permission.ErrExpiredToken
	}

	c, err := oauth.FindClient(inst, claims.Subject)
	if err != nil {
		if couchdb.IsInternalServerError(err) {
			return err
		}
		return permission.ErrInvalidToken
	}

	if c.ClientID != source.ClientID {
		return permission.ErrInvalidToken
	}
	if c.ClientSecret != source.ClientSecret {
		return permission.ErrInvalidToken
	}
	return nil
}

func checkSourceCode(inst *instance.Instance, code string) error {
	accessCode := &oauth.AccessCode{}
	if err := couchdb.GetDoc(inst, consts.OAuthAccessCodes, code, accessCode); err != nil {
		return permission.ErrInvalidToken
	}
	if accessCode.ClientID != SourceClientID {
		return permission.ErrInvalidToken
	}
	if accessCode.Scope != consts.ExportsRequests {
		return permission.ErrInvalidToken
	}
	return nil
}

// StartMove checks that the secret is known, sends a request to the other Cozy
// to block it during the move, and pushs a job for the export.
func StartMove(inst *instance.Instance, secret string) (*Request, error) {
	req, err := getStore().Get(inst, secret)
	if err != nil {
		return nil, err
	}

	u := req.ImportingURL() + "?source=" + inst.ContextualDomain()
	_, err = http.Post(u, "application/json", nil)
	if err != nil {
		return nil, errors.New("Cannot reach the other Cozy")
	}

	options := ExportOptions{
		ContextualDomain: inst.ContextualDomain(),
		TokenSource:      req.SourceCreds.Token,
		MoveTo: &MoveToOptions{
			URL:          req.Target,
			Token:        req.TargetCreds.Token,
			ClientID:     req.TargetCreds.ClientID,
			ClientSecret: req.TargetCreds.ClientSecret,
		},
	}
	msg, err := job.NewMessage(options)
	if err != nil {
		return nil, err
	}
	_, err = job.System().PushJob(inst, &job.JobRequest{
		WorkerType: "export",
		Message:    msg,
	})
	return req, err
}

// CallFinalize will call the /move/finalize endpoint on the other instance to
// unblock it after a successful move.
func CallFinalize(inst *instance.Instance, otherURL, token string) {
	u, err := url.Parse(otherURL)
	if err != nil {
		u, err = url.Parse("https://" + otherURL)
	}
	if err != nil {
		return
	}
	u.Path = "/move/finalize"
	req, err := http.NewRequest("POST", u.String(), nil)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: %s", err)
		return
	}
	req.Header.Add("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: %s", err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 204 {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort finalize: code=%d", res.StatusCode)
	}

	doc, err := inst.SettingsDocument()
	if err == nil {
		doc.M["moved_from"] = u.Host
		_ = couchdb.UpdateDoc(inst, doc)
	}
}

// Finalize makes the last steps on the source Cozy after the data has been
// successfully imported:
// - stop the konnectors
// - warn the OAuth clients
// - unblock the instance
func Finalize(inst *instance.Instance) error {
	var errm error
	sched := job.System()
	triggers, err := sched.GetAllTriggers(inst)
	if err == nil {
		for _, t := range triggers {
			infos := t.Infos()
			if infos.WorkerType == "konnector" {
				if err = sched.DeleteTrigger(inst, infos.TID); err != nil {
					errm = multierror.Append(errm, err)
				}
			}
		}
	} else {
		errm = multierror.Append(errm, err)
	}
	inst.Moved = true
	if err := lifecycle.Unblock(inst); err != nil {
		errm = multierror.Append(errm, err)
	}
	return errm
}

// Abort will call the /move/abort endpoint on the other instance to unblock it
// after a failed export or import during a move.
func Abort(inst *instance.Instance, otherURL, token string) {
	u, err := url.Parse(otherURL)
	if err != nil {
		u, err = url.Parse("https://" + otherURL)
	}
	if err != nil {
		return
	}
	u.Path = "/move/abort"
	req, err := http.NewRequest("POST", u.String(), nil)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: %s", err)
		return
	}
	req.Header.Add("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: %s", err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 204 {
		inst.Logger().
			WithField("nspace", "move").
			WithField("url", otherURL).
			Warnf("Cannort abort: code=%d", res.StatusCode)
	}
}
<|endoftext|>


<commit_before>package model_test

import (
	"encoding/json"
	"github.com/crezam/actions-on-google-golang/internal/test"
	"github.com/crezam/actions-on-google-golang/model"
	"os"
	"testing"
	"time"
)

func TestRequest1Parsing(t *testing.T) {

	var req model.ApiAiRequest

	file, _ := os.Open("./data/sample_request1.json")
	dec := json.NewDecoder(file)

	err := dec.Decode(&req)

	// test if any issues decoding file
	test.Ok(t, err)

	// assert correct parsing
	test.Equals(t, "209eefa7-adb5-4d03-a8b9-9f7ae68a0c11", req.Id)

	expectedTimestamp, _ := time.Parse(time.RFC3339Nano, "2016-10-10T07:41:40.098Z")
	test.Equals(t, expectedTimestamp, req.Timestamp)

	test.Equals(t, "Hi, my name is Sam!", req.Result.ResolvedQuery)
	test.Equals(t, "agent", req.Result.Source)
	test.Equals(t, "greetings", req.Result.Action)
	test.Equals(t, false, req.Result.ActionIncomplete)
	test.Equals(t, "Sam", req.Result.Parameters["user_name"])
	test.Equals(t, "", req.Result.Parameters["school"])

	test.Equals(t, "greetings", req.Result.Contexts[0].Name)
	test.Equals(t, "Sam", req.Result.Contexts[0].Parameters["user_name"])
	test.Equals(t, "Sam!", req.Result.Contexts[0].Parameters["user_name.original"])

	test.Equals(t, "373a354b-c15a-4a60-ac9d-a9f2aee76cb4", req.Result.Metadata.IntentID)
	test.Equals(t, "true", req.Result.Metadata.WebhookUsed)
	test.Equals(t, "greetings", req.Result.Metadata.IntentName)

	test.Equals(t, "Nice to meet you, Sam!", req.Result.Fulfillment.Speech)

	test.Equals(t, float64(1), req.Result.Score)

	test.Equals(t, "...", req.OriginalRequest.Data.User.UserID)
	test.Equals(t, "Sam", req.OriginalRequest.Data.User.Profile.DisplayName)
	test.Equals(t, "Sam", req.OriginalRequest.Data.User.Profile.GivenName)
	test.Equals(t, "Johnson", req.OriginalRequest.Data.User.Profile.FamilyName)

	test.Equals(t, "...", req.OriginalRequest.Data.User.AccessToken)

	test.Equals(t, 123.456, req.OriginalRequest.Data.Device.Location.Coordinates.Latitude)
	test.Equals(t, -123.456, req.OriginalRequest.Data.Device.Location.Coordinates.Longitude)

	test.Equals(t, "1234 Random Road, Anytown, CA 12345, United States", req.OriginalRequest.Data.Device.Location.FormattedAddress)
	test.Equals(t, "12345", req.OriginalRequest.Data.Device.Location.ZipCode)
	test.Equals(t, "Anytown", req.OriginalRequest.Data.Device.Location.City)

	test.Equals(t, 200, req.Status.Code)
	test.Equals(t, "success", req.Status.ErrorType)

	test.Equals(t, "37151f7c-a409-48b8-9890-cd980cd2548e", req.SessionID)
}

func TestRequest2Parsing(t *testing.T) {

	var req model.ApiAiRequest

	file, _ := os.Open("./data/sample_request2.json")
	dec := json.NewDecoder(file)

	err := dec.Decode(&req)

	// test if any issues decoding file
	test.Ok(t, err)

	// assert correct parsing
	test.Equals(t, "f4b72ee9-cabb-4acd-af9b-2d2cb6ff53d2", req.Id)

	expectedTimestamp, _ := time.Parse(time.RFC3339Nano, "2016-12-24T07:23:46.64Z")
	test.Equals(t, expectedTimestamp, req.Timestamp)

	test.Equals(t, "i wonder if you are ready for me?", req.Result.ResolvedQuery)
	test.Equals(t, "agent", req.Result.Source)
	test.Equals(t, "get_status", req.Result.Action)
	test.Equals(t, false, req.Result.ActionIncomplete)
	test.Equals(t, map[string]string{}, req.Result.Parameters)

	//TODO test Context

	test.Equals(t, "dd7f8d83-6f10-474f-b16c-d3dd3d071730", req.Result.Metadata.IntentID)
	test.Equals(t, "true", req.Result.Metadata.WebhookUsed)
	test.Equals(t, "false", req.Result.Metadata.WebhookForSlotFillingUsed)
	test.Equals(t, "get_status", req.Result.Metadata.IntentName)

}

<commit_msg>Add fullfillment tests<commit_after>package model_test

import (
	"encoding/json"
	"github.com/crezam/actions-on-google-golang/internal/test"
	"github.com/crezam/actions-on-google-golang/model"
	"os"
	"testing"
	"time"
)

func TestRequest1Parsing(t *testing.T) {

	var req model.ApiAiRequest

	file, _ := os.Open("./data/sample_request1.json")
	dec := json.NewDecoder(file)

	err := dec.Decode(&req)

	// test if any issues decoding file
	test.Ok(t, err)

	// assert correct parsing
	test.Equals(t, "209eefa7-adb5-4d03-a8b9-9f7ae68a0c11", req.Id)

	expectedTimestamp, _ := time.Parse(time.RFC3339Nano, "2016-10-10T07:41:40.098Z")
	test.Equals(t, expectedTimestamp, req.Timestamp)

	test.Equals(t, "Hi, my name is Sam!", req.Result.ResolvedQuery)
	test.Equals(t, "agent", req.Result.Source)
	test.Equals(t, "greetings", req.Result.Action)
	test.Equals(t, false, req.Result.ActionIncomplete)
	test.Equals(t, "Sam", req.Result.Parameters["user_name"])
	test.Equals(t, "", req.Result.Parameters["school"])

	test.Equals(t, "greetings", req.Result.Contexts[0].Name)
	test.Equals(t, "Sam", req.Result.Contexts[0].Parameters["user_name"])
	test.Equals(t, "Sam!", req.Result.Contexts[0].Parameters["user_name.original"])

	test.Equals(t, "373a354b-c15a-4a60-ac9d-a9f2aee76cb4", req.Result.Metadata.IntentID)
	test.Equals(t, "true", req.Result.Metadata.WebhookUsed)
	test.Equals(t, "greetings", req.Result.Metadata.IntentName)

	test.Equals(t, "Nice to meet you, Sam!", req.Result.Fulfillment.Speech)

	test.Equals(t, float64(1), req.Result.Score)

	test.Equals(t, "...", req.OriginalRequest.Data.User.UserID)
	test.Equals(t, "Sam", req.OriginalRequest.Data.User.Profile.DisplayName)
	test.Equals(t, "Sam", req.OriginalRequest.Data.User.Profile.GivenName)
	test.Equals(t, "Johnson", req.OriginalRequest.Data.User.Profile.FamilyName)

	test.Equals(t, "...", req.OriginalRequest.Data.User.AccessToken)

	test.Equals(t, 123.456, req.OriginalRequest.Data.Device.Location.Coordinates.Latitude)
	test.Equals(t, -123.456, req.OriginalRequest.Data.Device.Location.Coordinates.Longitude)

	test.Equals(t, "1234 Random Road, Anytown, CA 12345, United States", req.OriginalRequest.Data.Device.Location.FormattedAddress)
	test.Equals(t, "12345", req.OriginalRequest.Data.Device.Location.ZipCode)
	test.Equals(t, "Anytown", req.OriginalRequest.Data.Device.Location.City)

	test.Equals(t, 200, req.Status.Code)
	test.Equals(t, "success", req.Status.ErrorType)

	test.Equals(t, "37151f7c-a409-48b8-9890-cd980cd2548e", req.SessionID)
}

func TestRequest2Parsing(t *testing.T) {

	var req model.ApiAiRequest

	file, _ := os.Open("./data/sample_request2.json")
	dec := json.NewDecoder(file)

	err := dec.Decode(&req)

	// test if any issues decoding file
	test.Ok(t, err)

	// assert correct parsing
	test.Equals(t, "f4b72ee9-cabb-4acd-af9b-2d2cb6ff53d2", req.Id)

	expectedTimestamp, _ := time.Parse(time.RFC3339Nano, "2016-12-24T07:23:46.64Z")
	test.Equals(t, expectedTimestamp, req.Timestamp)

	test.Equals(t, "i wonder if you are ready for me?", req.Result.ResolvedQuery)
	test.Equals(t, "agent", req.Result.Source)
	test.Equals(t, "get_status", req.Result.Action)
	test.Equals(t, false, req.Result.ActionIncomplete)
	test.Equals(t, map[string]string{}, req.Result.Parameters)

	//TODO test Context

	test.Equals(t, "dd7f8d83-6f10-474f-b16c-d3dd3d071730", req.Result.Metadata.IntentID)
	test.Equals(t, "true", req.Result.Metadata.WebhookUsed)
	test.Equals(t, "false", req.Result.Metadata.WebhookForSlotFillingUsed)
	test.Equals(t, "get_status", req.Result.Metadata.IntentName)

	test.Equals(t, "", req.Result.Fulfillment.Speech)
	test.Equals(t, 0, req.Result.Fulfillment.Messages[0].Type)
	test.Equals(t, "", req.Result.Fulfillment.Messages[0].Speech)

}

<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	xsclient "github.com/xenserver/go-xenserver-client"
)

type StepFindVdi struct {
	VdiName       string
	ImagePathFunc func() string
	VdiUuidKey    string
}

func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packer.Ui)
	client := state.Get("client").(xsclient.XenAPIClient)

	// Ignore if VdiName is not specified
	if self.VdiName == "" {
		return multistep.ActionContinue
	} else if len(self.VdiName) >= 7 && self.VdiName[:7] == "uuid://" {
		vdiUuid:= self.VdiName[7:]
		_, err := client.GetVdiByUuid(vdiUuid)
		if err != nil {
			ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error()))
			return multistep.ActionHalt
		}
		state.Put(self.VdiUuidKey, vdiUuid)
		return multistep.ActionContinue
	}

	vdis, err := client.GetVdiByNameLabel(self.VdiName)

	switch {
	case len(vdis) == 0:
		ui.Error(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName))
		return multistep.ActionHalt
	case len(vdis) > 1:
		ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName))
		return multistep.ActionHalt
	}

	vdi := vdis[0]

	vdiUuid, err := vdi.GetUuid()
	if err != nil {
		ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error()))
		return multistep.ActionHalt
	}
	state.Put(self.VdiUuidKey, vdiUuid)

	return multistep.ActionContinue
}

func (self *StepFindVdi) Cleanup(state multistep.StateBag) {}
<commit_msg>Ran gofmt -s -w .<commit_after>package common

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	xsclient "github.com/xenserver/go-xenserver-client"
)

type StepFindVdi struct {
	VdiName       string
	ImagePathFunc func() string
	VdiUuidKey    string
}

func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packer.Ui)
	client := state.Get("client").(xsclient.XenAPIClient)

	// Ignore if VdiName is not specified
	if self.VdiName == "" {
		return multistep.ActionContinue
	} else if len(self.VdiName) >= 7 && self.VdiName[:7] == "uuid://" {
		vdiUuid := self.VdiName[7:]
		_, err := client.GetVdiByUuid(vdiUuid)
		if err != nil {
			ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error()))
			return multistep.ActionHalt
		}
		state.Put(self.VdiUuidKey, vdiUuid)
		return multistep.ActionContinue
	}

	vdis, err := client.GetVdiByNameLabel(self.VdiName)

	switch {
	case len(vdis) == 0:
		ui.Error(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName))
		return multistep.ActionHalt
	case len(vdis) > 1:
		ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName))
		return multistep.ActionHalt
	}

	vdi := vdis[0]

	vdiUuid, err := vdi.GetUuid()
	if err != nil {
		ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error()))
		return multistep.ActionHalt
	}
	state.Put(self.VdiUuidKey, vdiUuid)

	return multistep.ActionContinue
}

func (self *StepFindVdi) Cleanup(state multistep.StateBag) {}
<|endoftext|>


<commit_before>package models

import (
	"testing"
	"time"

	"github.com/antihax/goesi"
)

func TestAssetSetup(t *testing.T) {
	_, err := database.Exec(`
			INSERT IGNORE INTO evedata.assets VALUES
			 (60012526, 39, 2, 1001, "station", 21, "station", 0)
			,(60012526, 179, 2, 1001, "station", 22, "station", 0)
			,(60012526, 182, 2, 1001, "station", 23, "station", 0)
			,(23, 196, 2, 1001, "other", 24, "other", 0)
			,(23, 199, 2, 1001, "other", 25, "other", 0)
			,(22, 39, 2, 1001, "other", 26, "other", 0);
		`)
	if err != nil {
		t.Error(err)
		return
	}

	tok := goesi.CRESTToken{
		AccessToken:  "FAKE",
		RefreshToken: "So Fake",
		Expiry:       time.Now().UTC().Add(time.Hour * 100000),
		TokenType:    "Bearer"}

	err = AddCRESTToken(1001, 1001, "Dude", &tok, "")
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAssetLocations(t *testing.T) {
	_, err := GetAssetLocations(1001, 1)
	if err != nil {
		t.Error(err)
		return
	}
	_, err = GetAssetLocations(1001, 0)
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAssetCharacters(t *testing.T) {
	_, err := GetAssetCharacters(1)
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAsset(t *testing.T) {
	data, err := GetAssets(1001, 0, 60012526)
	if err != nil {
		t.Error(err)
		return
	}

	if data[0].CharacterID != 1001 {
		t.Error("wrong character ID")
		return
	}

	data, err = GetAssets(1001, 1001, 60012526)
	if err != nil {
		t.Error(err)
		return
	}

	if data[0].CharacterID != 1001 {
		t.Error("wrong character ID")
		return
	}

}
<commit_msg>check for recursion<commit_after>package models

import (
	"testing"
	"time"

	"github.com/antihax/goesi"
)

func TestAssetSetup(t *testing.T) {
	_, err := database.Exec(`
			INSERT IGNORE INTO evedata.assets VALUES
			 (60012526, 39, 2, 1001, "station", 21, "station", 0)
			,(60012526, 179, 2, 1001, "station", 22, "station", 0)
			,(60012526, 182, 2, 1001, "station", 23, "station", 0)
			,(23, 196, 2, 1001, "other", 24, "other", 0)
			,(23, 199, 2, 1001, "other", 25, "other", 0)
			,(22, 39, 2, 1001, "other", 26, "other", 0);
		`)
	if err != nil {
		t.Error(err)
		return
	}

	tok := goesi.CRESTToken{
		AccessToken:  "FAKE",
		RefreshToken: "So Fake",
		Expiry:       time.Now().UTC().Add(time.Hour * 100000),
		TokenType:    "Bearer"}

	err = AddCRESTToken(1001, 1001, "Dude", &tok, "")
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAssetLocations(t *testing.T) {
	_, err := GetAssetLocations(1001, 1)
	if err != nil {
		t.Error(err)
		return
	}
	_, err = GetAssetLocations(1001, 0)
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAssetCharacters(t *testing.T) {
	_, err := GetAssetCharacters(1)
	if err != nil {
		t.Error(err)
		return
	}
}

func TestGetAsset(t *testing.T) {
	data, err := GetAssets(1001, 0, 60012526)
	if err != nil {
		t.Error(err)
		return
	}

	if data[0].CharacterID != 1001 {
		t.Error("wrong character ID")
		return
	}

	subs := false
	for i := range data {
		if data[i].SubCount > 0 {
			subs = true
		}
	}

	if !subs {
		t.Error("failed recursion. should have some sub items.")
		return
	}

	data, err = GetAssets(1001, 1001, 60012526)
	if err != nil {
		t.Error(err)
		return
	}

	if data[0].CharacterID != 1001 {
		t.Error("wrong character ID")
		return
	}

	subs = false
	for i := range data {
		if data[i].SubCount > 0 {
			subs = true
		}
	}

	if !subs {
		t.Error("failed recursion. should have some sub items.")
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"os"
	"os/exec"
	"strings"
)

type pingServer struct {
	connection *net.TCPConn
}

func (pingServer *pingServer) start() {
	connReader := bufio.NewReader(pingServer.connection)
	for {
		line, err := connReader.ReadString('\n')
		if errorCheck(err) {
			break
		}
		fmt.Println(line)
		ip := net.ParseIP(strings.Replace(line, "\n", "", -1))
		if ip == nil {
			fmt.Fprintln(os.Stderr, "Could not parse ip address, skipping")
			continue
		}
		ping := exec.Command("ping", ip.String(), "-c", "3", "-l", "3")

		out, err := ping.Output()
		if errorCheck(err) {
			continue
		}
		split0 := bytes.Split(out, []byte("\n"))
		if len(split0) < 2 {
			fmt.Fprintln(os.Stderr, "Could not parse ping lines: ", string(out))
			continue
		}
		split1 := bytes.Fields(split0[len(split0)-2])
		if len(split1) != 7 {
			fmt.Fprintln(os.Stderr, "Could not parse ping output: ", string(out))
			continue
		}
		split2 := bytes.Fields(split1[3])
		fmt.Println(len(split2), "||", string(split1[3]))
		if len(split2) != 4 {
			fmt.Fprintln(os.Stderr, "Could not parse ping average: ", string(out))
			continue
		}
		avg := split2[1]
		pingServer.connection.Write(append([]byte(ip.String()+" "), append(avg, '\n')...))
	}
}
<commit_msg>further ping tweaks... this is what happens when you have to develop on a pc but compile on a linux<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"os"
	"os/exec"
	"strings"
)

type pingServer struct {
	connection *net.TCPConn
}

func (pingServer *pingServer) start() {
	connReader := bufio.NewReader(pingServer.connection)
	for {
		line, err := connReader.ReadString('\n')
		if errorCheck(err) {
			break
		}
		fmt.Println(line)
		ip := net.ParseIP(strings.Replace(line, "\n", "", -1))
		if ip == nil {
			fmt.Fprintln(os.Stderr, "Could not parse ip address, skipping")
			continue
		}
		ping := exec.Command("ping", ip.String(), "-c", "3", "-l", "3")

		out, err := ping.Output()
		if errorCheck(err) {
			continue
		}
		split0 := bytes.Split(out, []byte("\n"))
		if len(split0) < 2 {
			fmt.Fprintln(os.Stderr, "Could not parse ping lines: ", string(out))
			continue
		}
		split1 := bytes.Fields(split0[len(split0)-2])
		if len(split1) != 7 {
			fmt.Fprintln(os.Stderr, "Could not parse ping output: ", string(out))
			continue
		}
		split2 := bytes.Split(split1[3], []byte("/"))
		fmt.Println(len(split2), "||", string(split1[3]))
		if len(split2) != 4 {
			fmt.Fprintln(os.Stderr, "Could not parse ping average: ", string(out))
			continue
		}
		avg := split2[1]
		pingServer.connection.Write(append([]byte(ip.String()+" "), append(avg, '\n')...))
	}
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"flag"
	"fmt"
	"os"
	"text/template"

	"github.com/01org/ciao/ciao-controller/types"
	"github.com/01org/ciao/templateutils"
)

var nodeCommand = &command{
	SubCommands: map[string]subCommand{
		"list":   new(nodeListCommand),
		"status": new(nodeStatusCommand),
		"show":   new(nodeShowCommand),
	},
}

type nodeListCommand struct {
	Flag     flag.FlagSet
	compute  bool
	cnci     bool
	nodeID   bool
	template string
}

func (cmd *nodeListCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node list

List nodes

The list flags are:
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, `
The template passed to the -f option operates on one of the following types:

--cnci

%s

--compute

%s`,
		templateutils.GenerateUsageUndecorated([]types.CiaoCNCI{}),
		templateutils.GenerateUsageUndecorated([]types.CiaoComputeNode{}))
	fmt.Fprintln(os.Stderr, templateutils.TemplateFunctionHelp(nil))
	os.Exit(2)
}

func (cmd *nodeListCommand) parseArgs(args []string) []string {
	cmd.Flag.BoolVar(&cmd.compute, "compute", false, "List all compute nodes")
	cmd.Flag.BoolVar(&cmd.cnci, "cnci", false, "List all CNCIs")
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeListCommand) run(args []string) error {
	var t *template.Template
	if cmd.template != "" {
		var err error
		t, err = templateutils.CreateTemplate("node-list", cmd.template, nil)
		if err != nil {
			fatalf(err.Error())
		}
	}

	if cmd.compute {
		return listComputeNodes(t)
	}
	if cmd.cnci {
		return listCNCINodes(t)
	}
	cmd.usage()
	return nil
}

func listComputeNodes(t *template.Template) error {
	var nodes types.CiaoComputeNodes

	url := buildComputeURL("nodes")

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &nodes)
	if err != nil {
		fatalf(err.Error())
	}

	if t != nil {
		if err := t.Execute(os.Stdout, &nodes.Nodes); err != nil {
			fatalf(err.Error())
		}
		return nil
	}

	for i, node := range nodes.Nodes {
		fmt.Printf("Compute Node %d\n", i+1)
		fmt.Printf("\tUUID: %s\n", node.ID)
		fmt.Printf("\tStatus: %s\n", node.Status)
		fmt.Printf("\tLoad: %d\n", node.Load)
		fmt.Printf("\tAvailable/Total memory: %d/%d MB\n", node.MemAvailable, node.MemTotal)
		fmt.Printf("\tAvailable/Total disk: %d/%d MB\n", node.DiskAvailable, node.DiskTotal)
		fmt.Printf("\tTotal Instances: %d\n", node.TotalInstances)
		fmt.Printf("\t\tRunning Instances: %d\n", node.TotalRunningInstances)
		fmt.Printf("\t\tPending Instances: %d\n", node.TotalPendingInstances)
		fmt.Printf("\t\tPaused Instances: %d\n", node.TotalPausedInstances)
	}
	return nil
}

func listCNCINodes(t *template.Template) error {
	var cncis types.CiaoCNCIs

	url := buildComputeURL("cncis")

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &cncis)
	if err != nil {
		fatalf(err.Error())
	}

	if t != nil {
		if err := t.Execute(os.Stdout, &cncis.CNCIs); err != nil {
			fatalf(err.Error())
		}
		return nil
	}

	for i, cnci := range cncis.CNCIs {
		fmt.Printf("CNCI %d\n", i+1)
		fmt.Printf("\tCNCI UUID: %s\n", cnci.ID)
		fmt.Printf("\tTenant UUID: %s\n", cnci.TenantID)
		fmt.Printf("\tIPv4: %s\n", cnci.IPv4)
		fmt.Printf("\tSubnets:\n")
		for _, subnet := range cnci.Subnets {
			fmt.Printf("\t\t%s\n", subnet.Subnet)
		}
	}
	return nil
}

type nodeStatusCommand struct {
	Flag     flag.FlagSet
	template string
}

func (cmd *nodeStatusCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node status

Show cluster status
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "\n%s",
		templateutils.GenerateUsageDecorated("f", types.CiaoClusterStatus{}.Status, nil))
	os.Exit(2)
}

func (cmd *nodeStatusCommand) parseArgs(args []string) []string {
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeStatusCommand) run(args []string) error {
	var status types.CiaoClusterStatus
	url := buildComputeURL("nodes/summary")

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &status)
	if err != nil {
		fatalf(err.Error())
	}

	if cmd.template != "" {
		return templateutils.OutputToTemplate(os.Stdout, "node-status", cmd.template,
			&status.Status, nil)
	}

	fmt.Printf("Total Nodes %d\n", status.Status.TotalNodes)
	fmt.Printf("\tReady %d\n", status.Status.TotalNodesReady)
	fmt.Printf("\tFull %d\n", status.Status.TotalNodesFull)
	fmt.Printf("\tOffline %d\n", status.Status.TotalNodesOffline)
	fmt.Printf("\tMaintenance %d\n", status.Status.TotalNodesMaintenance)

	return nil
}

type nodeShowCommand struct {
	Flag     flag.FlagSet
	cnci     bool
	nodeID   string
	template string
}

func (cmd *nodeShowCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node show

Show info about a node

The show flags are:
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, `
The template passed to the -f option operates on one of the following types:

--cnci

%s`, templateutils.GenerateUsageUndecorated(types.CiaoCNCI{}))
	fmt.Fprintln(os.Stderr, templateutils.TemplateFunctionHelp(nil))
	os.Exit(2)
}

func (cmd *nodeShowCommand) parseArgs(args []string) []string {
	cmd.Flag.BoolVar(&cmd.cnci, "cnci", false, "Show info about a cnci node")
	cmd.Flag.StringVar(&cmd.nodeID, "node-id", "", "Node ID")
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeShowCommand) run(args []string) error {
	if cmd.cnci {
		return showCNCINode(cmd)
	}

	cmd.usage()
	return nil
}

func showCNCINode(cmd *nodeShowCommand) error {
	if cmd.nodeID == "" {
		fatalf("Missing required -cnci-id parameter")
	}

	var cnci types.CiaoCNCI

	url := buildComputeURL("cncis/%s/detail", cmd.nodeID)

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &cnci)
	if err != nil {
		fatalf(err.Error())
	}

	if cmd.template != "" {
		return templateutils.OutputToTemplate(os.Stdout, "node-show", cmd.template,
			&cnci, nil)
	}

	fmt.Printf("\tCNCI UUID: %s\n", cnci.ID)
	fmt.Printf("\tTenant UUID: %s\n", cnci.TenantID)
	fmt.Printf("\tIPv4: %s\n", cnci.IPv4)
	fmt.Printf("\tSubnets:\n")
	for _, subnet := range cnci.Subnets {
		fmt.Printf("\t\t%s\n", subnet.Subnet)
	}
	return nil
}
<commit_msg>ciao-cli: allow listing of all/compute/network nodes<commit_after>//
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"flag"
	"fmt"
	"os"
	"text/template"

	"github.com/01org/ciao/ciao-controller/types"
	"github.com/01org/ciao/templateutils"
)

var nodeCommand = &command{
	SubCommands: map[string]subCommand{
		"list":   new(nodeListCommand),
		"status": new(nodeStatusCommand),
		"show":   new(nodeShowCommand),
	},
}

type nodeListCommand struct {
	Flag     flag.FlagSet
	compute  bool
	network  bool
	all      bool
	cnci     bool
	nodeID   bool
	template string
}

func (cmd *nodeListCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node list

List nodes

The list flags are:
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, `
The template passed to the -f option operates on one of the following types:

--cnci

%s

--network

%s

--compute

%s

--all

%s`,
		templateutils.GenerateUsageUndecorated([]types.CiaoCNCI{}),
		templateutils.GenerateUsageUndecorated([]types.CiaoNode{}),
		templateutils.GenerateUsageUndecorated([]types.CiaoNode{}),
		templateutils.GenerateUsageUndecorated([]types.CiaoNode{}))
	fmt.Fprintln(os.Stderr, templateutils.TemplateFunctionHelp(nil))
	os.Exit(2)
}

func (cmd *nodeListCommand) parseArgs(args []string) []string {
	cmd.Flag.BoolVar(&cmd.compute, "compute", false, "List all compute nodes")
	cmd.Flag.BoolVar(&cmd.network, "network", false, "List all network nodes")
	cmd.Flag.BoolVar(&cmd.all, "all", false, "List all nodes")
	cmd.Flag.BoolVar(&cmd.cnci, "cnci", false, "List all CNCIs")
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeListCommand) run(args []string) error {
	var t *template.Template
	if cmd.template != "" {
		var err error
		t, err = templateutils.CreateTemplate("node-list", cmd.template, nil)
		if err != nil {
			fatalf(err.Error())
		}
	}

	if cmd.compute {
		listComputeNodes(t)
	} else if cmd.network {
		listNetworkNodes(t)
	} else if cmd.cnci {
		listCNCINodes(t)
	} else if cmd.all {
		listNodes(t)
	} else {
		cmd.usage()
	}
	return nil
}

func dumpNodes(headerText string, url string, t *template.Template) {
	var nodes types.CiaoNodes

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &nodes)
	if err != nil {
		fatalf(err.Error())
	}

	if t != nil {
		if err := t.Execute(os.Stdout, &nodes.Nodes); err != nil {
			fatalf(err.Error())
		}
		return
	}

	for i, node := range nodes.Nodes {
		fmt.Printf("%s %d\n", headerText, i+1)
		fmt.Printf("\tUUID: %s\n", node.ID)
		fmt.Printf("\tStatus: %s\n", node.Status)
		fmt.Printf("\tLoad: %d\n", node.Load)
		fmt.Printf("\tAvailable/Total memory: %d/%d MB\n", node.MemAvailable, node.MemTotal)
		fmt.Printf("\tAvailable/Total disk: %d/%d MB\n", node.DiskAvailable, node.DiskTotal)
		fmt.Printf("\tTotal Instances: %d\n", node.TotalInstances)
		fmt.Printf("\t\tRunning Instances: %d\n", node.TotalRunningInstances)
		fmt.Printf("\t\tPending Instances: %d\n", node.TotalPendingInstances)
		fmt.Printf("\t\tPaused Instances: %d\n", node.TotalPausedInstances)
	}
}

func listComputeNodes(t *template.Template) {
	url := buildComputeURL("nodes/compute")
	dumpNodes("Compute Node", url, t)
}

func listNetworkNodes(t *template.Template) {
	url := buildComputeURL("nodes/network")
	dumpNodes("Network Node", url, t)
}

func listNodes(t *template.Template) {
	url := buildComputeURL("nodes")
	dumpNodes("Node", url, t)
}

func listCNCINodes(t *template.Template) error {
	var cncis types.CiaoCNCIs

	url := buildComputeURL("cncis")

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &cncis)
	if err != nil {
		fatalf(err.Error())
	}

	if t != nil {
		if err := t.Execute(os.Stdout, &cncis.CNCIs); err != nil {
			fatalf(err.Error())
		}
		return nil
	}

	for i, cnci := range cncis.CNCIs {
		fmt.Printf("CNCI %d\n", i+1)
		fmt.Printf("\tCNCI UUID: %s\n", cnci.ID)
		fmt.Printf("\tTenant UUID: %s\n", cnci.TenantID)
		fmt.Printf("\tIPv4: %s\n", cnci.IPv4)
		fmt.Printf("\tSubnets:\n")
		for _, subnet := range cnci.Subnets {
			fmt.Printf("\t\t%s\n", subnet.Subnet)
		}
	}
	return nil
}

type nodeStatusCommand struct {
	Flag     flag.FlagSet
	template string
}

func (cmd *nodeStatusCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node status

Show cluster status
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "\n%s",
		templateutils.GenerateUsageDecorated("f", types.CiaoClusterStatus{}.Status, nil))
	os.Exit(2)
}

func (cmd *nodeStatusCommand) parseArgs(args []string) []string {
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeStatusCommand) run(args []string) error {
	var status types.CiaoClusterStatus
	url := buildComputeURL("nodes/summary")

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &status)
	if err != nil {
		fatalf(err.Error())
	}

	if cmd.template != "" {
		return templateutils.OutputToTemplate(os.Stdout, "node-status", cmd.template,
			&status.Status, nil)
	}

	fmt.Printf("Total Nodes %d\n", status.Status.TotalNodes)
	fmt.Printf("\tReady %d\n", status.Status.TotalNodesReady)
	fmt.Printf("\tFull %d\n", status.Status.TotalNodesFull)
	fmt.Printf("\tOffline %d\n", status.Status.TotalNodesOffline)
	fmt.Printf("\tMaintenance %d\n", status.Status.TotalNodesMaintenance)

	return nil
}

type nodeShowCommand struct {
	Flag     flag.FlagSet
	cnci     bool
	nodeID   string
	template string
}

func (cmd *nodeShowCommand) usage(...string) {
	fmt.Fprintf(os.Stderr, `usage: ciao-cli [options] node show

Show info about a node

The show flags are:
`)
	cmd.Flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, `
The template passed to the -f option operates on one of the following types:

--cnci

%s`, templateutils.GenerateUsageUndecorated(types.CiaoCNCI{}))
	fmt.Fprintln(os.Stderr, templateutils.TemplateFunctionHelp(nil))
	os.Exit(2)
}

func (cmd *nodeShowCommand) parseArgs(args []string) []string {
	cmd.Flag.BoolVar(&cmd.cnci, "cnci", false, "Show info about a cnci node")
	cmd.Flag.StringVar(&cmd.nodeID, "node-id", "", "Node ID")
	cmd.Flag.StringVar(&cmd.template, "f", "", "Template used to format output")
	cmd.Flag.Usage = func() { cmd.usage() }
	cmd.Flag.Parse(args)
	return cmd.Flag.Args()
}

func (cmd *nodeShowCommand) run(args []string) error {
	if cmd.cnci {
		return showCNCINode(cmd)
	}

	cmd.usage()
	return nil
}

func showCNCINode(cmd *nodeShowCommand) error {
	if cmd.nodeID == "" {
		fatalf("Missing required -cnci-id parameter")
	}

	var cnci types.CiaoCNCI

	url := buildComputeURL("cncis/%s/detail", cmd.nodeID)

	resp, err := sendHTTPRequest("GET", url, nil, nil)
	if err != nil {
		fatalf(err.Error())
	}

	err = unmarshalHTTPResponse(resp, &cnci)
	if err != nil {
		fatalf(err.Error())
	}

	if cmd.template != "" {
		return templateutils.OutputToTemplate(os.Stdout, "node-show", cmd.template,
			&cnci, nil)
	}

	fmt.Printf("\tCNCI UUID: %s\n", cnci.ID)
	fmt.Printf("\tTenant UUID: %s\n", cnci.TenantID)
	fmt.Printf("\tIPv4: %s\n", cnci.IPv4)
	fmt.Printf("\tSubnets:\n")
	for _, subnet := range cnci.Subnets {
		fmt.Printf("\t\t%s\n", subnet.Subnet)
	}
	return nil
}
<|endoftext|>


<commit_before>package context

import "C"

const (
	KEYCODE_LEFT = iota
	KEYCODE_RIGHT
	KEYCODE_UP
	KEYCODE_DOWN
	KEYCODE_UNKNOWN
)

var (
	OnInit    (func())
	OnExit    (func())
	OnSize    (func(w int32, h int32))
	OnDraw    (func())
	OnKeyDown (func(kc int))
	OnKeyUp   (func(kc int))
)

//export onInit
func onInit() {
	f := OnInit
	if f != nil {
		f()
	}
}

//export onExit
func onExit() {
	f := OnExit
	if f != nil {
		f()
	}
}

//export onSize
func onSize(w, h int32) {
	f := OnSize
	if f != nil {
		f(w, h)
	}
}

//export onDraw
func onDraw() {
	f := OnDraw
	if f != nil {
		f()
	}
}

//export onKeyDown
func onKeyDown(kc int32) {
	f := OnKeyDown
	if f != nil {
		f(int(kc))
	}
}

//export onKeyUp
func onKeyUp(kc int32) {
	f := OnKeyUp
	if f != nil {
		f(int(kc))
	}
}
<commit_msg>Update event.go<commit_after>package context

import "C"

const (
	KEYCODE_LEFT = iota
	KEYCODE_RIGHT
	KEYCODE_UP
	KEYCODE_DOWN
	KEYCODE_SHIFT
	KEYCODE_UNKNOWN
)

var (
	OnInit    (func())
	OnExit    (func())
	OnSize    (func(w int32, h int32))
	OnDraw    (func())
	OnKeyDown (func(kc int))
	OnKeyUp   (func(kc int))
)

//export onInit
func onInit() {
	f := OnInit
	if f != nil {
		f()
	}
}

//export onExit
func onExit() {
	f := OnExit
	if f != nil {
		f()
	}
}

//export onSize
func onSize(w, h int32) {
	f := OnSize
	if f != nil {
		f(w, h)
	}
}

//export onDraw
func onDraw() {
	f := OnDraw
	if f != nil {
		f()
	}
}

//export onKeyDown
func onKeyDown(kc int32) {
	f := OnKeyDown
	if f != nil {
		f(int(kc))
	}
}

//export onKeyUp
func onKeyUp(kc int32) {
	f := OnKeyUp
	if f != nil {
		f(int(kc))
	}
}
<|endoftext|>