<commit_before>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 workflow

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-gocommon/gocommon"
)

type TriggerDB struct {
	*ResourceDB
	mapping string
}

func NewTriggerDB(service *Service, esi elasticsearch.IIndex) (*TriggerDB, error) {
	rdb, err := NewResourceDB(service, esi)
	if err != nil {
		return nil, err
	}
	ardb := TriggerDB{ResourceDB: rdb, mapping: TriggerDBMapping}
	return &ardb, nil
}

func (db *TriggerDB) PostData(trigger *Trigger) error {
	{ //CHECK SERVICE EXISTS
		serviceID := trigger.Job.JobType.Data["serviceId"]
		strServiceID, ok := serviceID.(string)
		if !ok {
			return LoggedError("TriggerDB.PostData failed: serviceId field not of type string")
		}

		if domain := os.Getenv("DOMAIN"); domain != "" {

			serviceControllerURL := db.service.sys.BindTo + "pz-servicecontroller." + domain
			log.Println(serviceControllerURL)
			// TODO:
			// if err is nil, we have a servicecontroller to talk to
			// if err is not nil, we'll assume we are mocking (which means
			// we have no servicecontroller client to mock)
			response, err := http.Get(fmt.Sprintf("%s/service/%s", serviceControllerURL, strServiceID))
			if err != nil {
				return LoggedError("TriggerDB.PostData failed to make request to ServiceController: %s", err)
			}
			// On error, this should close on it's own
			defer func() {
				err = response.Body.Close()
				if err != nil {
					panic(err) // TODO: defer doesn't handle errs well
				}
			}()
			if response.StatusCode != 200 {
				return LoggedError("TriggerDB.PostData failed: serviceID %s does not exist", strServiceID)
			}
		}
	}

	//log.Printf("Query: %v", wrapper)
	body, err := json.Marshal(trigger.Condition)
	if err != nil {
		return err
	}

	//log.Printf("Posting percolation query: %s", body)
	indexResult, err := db.service.eventDB.Esi.AddPercolationQuery(trigger.TriggerID.String(), piazza.JsonString(body))
	if err != nil {
		var errMessage string
		if strings.Contains(err.Error(), "elastic: Error 500 (Internal Server Error): failed to parse query") {
			errMessage = fmt.Sprintf("TriggerDB.PostData addpercquery failed: elastic failed to parse query. Common causes: [Variables do not start with 'data.' or are not found at your specified path, invalid perc query structure].")
		} else {
			errMessage = fmt.Sprintf("TriggerDB.PostData addpercquery failed [unknown cause]: %s ", err)
		}
		return LoggedError(errMessage)
	}
	if indexResult == nil {
		return LoggedError("TriggerDB.PostData addpercquery failed: no indexResult")
	}
	if !indexResult.Created {
		return LoggedError("TriggerDB.PostData addpercquery failed: not created")
	}

	//log.Printf("percolation query added: ID: %s, Type: %s, Index: %s", indexResult.Id, indexResult.Type, indexResult.Index)
	//log.Printf("percolation id: %s", indexResult.Id)
	trigger.PercolationID = piazza.Ident(indexResult.ID)

	trigger.Condition = encodeCondition(trigger.Condition).(map[string]interface{})

	indexResult2, err := db.Esi.PostData(db.mapping, trigger.TriggerID.String(), trigger)
	if err != nil {
		_, _ = db.service.eventDB.Esi.DeletePercolationQuery(trigger.TriggerID.String())
		return LoggedError("TriggerDB.PostData failed: %s", err)
	}
	if !indexResult2.Created {
		_, _ = db.service.eventDB.Esi.DeletePercolationQuery(trigger.TriggerID.String())
		return LoggedError("TriggerDB.PostData failed: not created")
	}

	return nil
}

func (db *TriggerDB) PutTrigger(trigger *Trigger, update *TriggerUpdate, actor string) (*Trigger, error) {
	trigger.Enabled = update.Enabled
	strTrigger, err := piazza.StructInterfaceToString(*trigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	intTrigger, err := piazza.StructStringToInterface(strTrigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	mapTrigger, ok := intTrigger.(map[string]interface{})
	if !ok {
		return trigger, LoggedError("TriggerDB.PutData failed: bad trigger")
	}
	fixedTrigger := handleDotTilde(mapTrigger, func(in string) string { return strings.Replace(in, ".", "~", -1) })

	_, err = db.Esi.PutData(db.mapping, trigger.TriggerID.String(), fixedTrigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	return trigger, nil
}

func (db *TriggerDB) GetAll(format *piazza.JsonPagination, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.FilterByMatchAll(db.mapping, format)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetAll failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetAll failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	for i, trigger := range triggers {
		triggers[i].Condition = decodeCondition(trigger.Condition).(map[string]interface{})
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) GetTriggersByDslQuery(dslString string, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.SearchByJSON(db.mapping, dslString)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByDslQuery failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByDslQuery failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) GetOne(id piazza.Ident, actor string) (*Trigger, bool, error) {
	getResult, err := db.Esi.GetByID(db.mapping, id.String())
	if err != nil {
		return nil, false, LoggedError("TriggerDB.GetOne failed: %s", err)
	}
	if getResult == nil {
		return nil, false, LoggedError("TriggerDB.GetOne failed: no getResult")
	}

	src := getResult.Source
	var trigger Trigger
	if err = json.Unmarshal(*src, &trigger); err != nil {
		return nil, getResult.Found, LoggedError("TriggerDB.GetOne failed: %s", err)
	}

	trigger.Condition = decodeCondition(trigger.Condition).(map[string]interface{})

	return &trigger, getResult.Found, nil
}

func (db *TriggerDB) GetTriggersByEventTypeID(format *piazza.JsonPagination, id piazza.Ident, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.FilterByTermQuery(db.mapping, "eventTypeId", id, format)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByEventTypeId failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByEventTypeId failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) DeleteTrigger(id piazza.Ident, actor string) (bool, error) {
	trigger, found, err := db.GetOne(id, actor)
	if err != nil {
		return found, err
	}
	if trigger == nil {
		return false, nil
	}

	deleteResult, err := db.Esi.DeleteByID(db.mapping, string(id))
	if err != nil {
		return deleteResult.Found, LoggedError("TriggerDB.DeleteById failed: %s", err)
	}
	if deleteResult == nil {
		return false, LoggedError("TriggerDB.DeleteById failed: no deleteResult")
	}
	if !deleteResult.Found {
		return false, nil
	}

	deleteResult2, err := db.service.eventDB.Esi.DeletePercolationQuery(string(trigger.PercolationID))
	if err != nil {
		return deleteResult2.Found, LoggedError("TriggerDB.DeleteById percquery failed: %s", err)
	}
	if deleteResult2 == nil {
		return false, LoggedError("TriggerDB.DeleteById percquery failed: no deleteResult")
	}

	return deleteResult2.Found, nil
}

func encodeCondition(in interface{}) interface{} {
	return handleDotTilde(in, func(in string) string { return strings.Replace(in, ".", "~", -1) })

}
func decodeCondition(in interface{}) interface{} {
	in = handleDotTilde(in, func(in string) string { return strings.Replace(in, "~", ".", -1) })
	return handleUniqueParams(in, "unusedEventTypeName", func(eventTypeName string, key string) string {
		firstPart := key[:strings.Index(key, ".")+1]
		rest := key[strings.Index(key, ".")+1:]
		return firstPart + rest[strings.Index(rest, ".")+1:]
	})
}

func handleDotTilde(input interface{}, replace func(string) string) interface{} {
	var output interface{}
	switch input.(type) {
	case map[string]interface{}:
		output = handleDotTildeMap(input.(map[string]interface{}), replace)
	case []interface{}:
		output = handleDotTildeArr(input.([]interface{}), replace)
	}
	return output
}
func handleDotTildeMap(inputObj map[string]interface{}, replace func(string) string) map[string]interface{} {
	outputObj := map[string]interface{}{}
	for k, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj[k] = handleDotTildeArr(v.([]interface{}), replace)
		case map[string]interface{}:
			outputObj[k] = handleDotTildeMap(v.(map[string]interface{}), replace)
		default:
			outputObj[replace(k)] = v
		}
	}
	return outputObj
}
func handleDotTildeArr(inputObj []interface{}, replace func(string) string) []interface{} {
	outputObj := []interface{}{}
	for _, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj = append(outputObj, handleDotTildeArr(v.([]interface{}), replace))
		case map[string]interface{}:
			outputObj = append(outputObj, handleDotTildeMap(v.(map[string]interface{}), replace))
		default:
			outputObj = append(outputObj, v)
		}
	}
	return outputObj
}

func handleUniqueParams(input interface{}, eventTypeName string, getKey func(string, string) string) interface{} {
	var output interface{}
	switch input.(type) {
	case map[string]interface{}:
		output = handleUniqueParamsMap(input.(map[string]interface{}), eventTypeName, getKey)
	case []interface{}:
		output = handleUniqueParamsArr(input.([]interface{}), eventTypeName, getKey)
	}
	return output
}

func handleUniqueParamsMap(inputObj map[string]interface{}, eventTypeName string, getKey func(string, string) string) map[string]interface{} {
	outputObj := map[string]interface{}{}
	for k, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj[k] = handleUniqueParamsArr(v.([]interface{}), eventTypeName, getKey)
		case map[string]interface{}:
			outputObj[k] = handleUniqueParamsMap(v.(map[string]interface{}), eventTypeName, getKey)
		default:
			outputObj[getKey(eventTypeName, k)] = v
		}
	}
	return outputObj
}

func handleUniqueParamsArr(inputObj []interface{}, eventTypeName string, getKey func(string, string) string) []interface{} {
	outputObj := []interface{}{}
	for _, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj = append(outputObj, handleUniqueParamsArr(v.([]interface{}), eventTypeName, getKey))
		case map[string]interface{}:
			outputObj = append(outputObj, handleUniqueParamsMap(v.(map[string]interface{}), eventTypeName, getKey))
		default:
			outputObj = append(outputObj, v)
		}
	}
	return outputObj
}
<commit_msg>debug<commit_after>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 workflow

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-gocommon/gocommon"
)

type TriggerDB struct {
	*ResourceDB
	mapping string
}

func NewTriggerDB(service *Service, esi elasticsearch.IIndex) (*TriggerDB, error) {
	rdb, err := NewResourceDB(service, esi)
	if err != nil {
		return nil, err
	}
	ardb := TriggerDB{ResourceDB: rdb, mapping: TriggerDBMapping}
	return &ardb, nil
}

func (db *TriggerDB) PostData(trigger *Trigger) error {
	{ //CHECK SERVICE EXISTS
		serviceID := trigger.Job.JobType.Data["serviceId"]
		strServiceID, ok := serviceID.(string)
		if !ok {
			return LoggedError("TriggerDB.PostData failed: serviceId field not of type string")
		}
		log.Println("domain", os.Getenv("DOMAIN"))
		if domain := os.Getenv("DOMAIN"); domain != "" {
			serviceControllerURL := db.service.sys.BindTo + "pz-servicecontroller." + domain
			log.Println(serviceControllerURL)
			// TODO:
			// if err is nil, we have a servicecontroller to talk to
			// if err is not nil, we'll assume we are mocking (which means
			// we have no servicecontroller client to mock)
			response, err := http.Get(fmt.Sprintf("%s/service/%s", serviceControllerURL, strServiceID))
			if err != nil {
				return LoggedError("TriggerDB.PostData failed to make request to ServiceController: %s", err)
			}
			// On error, this should close on it's own
			defer func() {
				err = response.Body.Close()
				if err != nil {
					panic(err) // TODO: defer doesn't handle errs well
				}
			}()
			if response.StatusCode != 200 {
				return LoggedError("TriggerDB.PostData failed: serviceID %s does not exist", strServiceID)
			}
		}
	}

	//log.Printf("Query: %v", wrapper)
	body, err := json.Marshal(trigger.Condition)
	if err != nil {
		return err
	}

	//log.Printf("Posting percolation query: %s", body)
	indexResult, err := db.service.eventDB.Esi.AddPercolationQuery(trigger.TriggerID.String(), piazza.JsonString(body))
	if err != nil {
		var errMessage string
		if strings.Contains(err.Error(), "elastic: Error 500 (Internal Server Error): failed to parse query") {
			errMessage = fmt.Sprintf("TriggerDB.PostData addpercquery failed: elastic failed to parse query. Common causes: [Variables do not start with 'data.' or are not found at your specified path, invalid perc query structure].")
		} else {
			errMessage = fmt.Sprintf("TriggerDB.PostData addpercquery failed [unknown cause]: %s ", err)
		}
		return LoggedError(errMessage)
	}
	if indexResult == nil {
		return LoggedError("TriggerDB.PostData addpercquery failed: no indexResult")
	}
	if !indexResult.Created {
		return LoggedError("TriggerDB.PostData addpercquery failed: not created")
	}

	//log.Printf("percolation query added: ID: %s, Type: %s, Index: %s", indexResult.Id, indexResult.Type, indexResult.Index)
	//log.Printf("percolation id: %s", indexResult.Id)
	trigger.PercolationID = piazza.Ident(indexResult.ID)

	trigger.Condition = encodeCondition(trigger.Condition).(map[string]interface{})

	indexResult2, err := db.Esi.PostData(db.mapping, trigger.TriggerID.String(), trigger)
	if err != nil {
		_, _ = db.service.eventDB.Esi.DeletePercolationQuery(trigger.TriggerID.String())
		return LoggedError("TriggerDB.PostData failed: %s", err)
	}
	if !indexResult2.Created {
		_, _ = db.service.eventDB.Esi.DeletePercolationQuery(trigger.TriggerID.String())
		return LoggedError("TriggerDB.PostData failed: not created")
	}

	return nil
}

func (db *TriggerDB) PutTrigger(trigger *Trigger, update *TriggerUpdate, actor string) (*Trigger, error) {
	trigger.Enabled = update.Enabled
	strTrigger, err := piazza.StructInterfaceToString(*trigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	intTrigger, err := piazza.StructStringToInterface(strTrigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	mapTrigger, ok := intTrigger.(map[string]interface{})
	if !ok {
		return trigger, LoggedError("TriggerDB.PutData failed: bad trigger")
	}
	fixedTrigger := handleDotTilde(mapTrigger, func(in string) string { return strings.Replace(in, ".", "~", -1) })

	_, err = db.Esi.PutData(db.mapping, trigger.TriggerID.String(), fixedTrigger)
	if err != nil {
		return trigger, LoggedError("TriggerDB.PutData failed: %s", err)
	}
	return trigger, nil
}

func (db *TriggerDB) GetAll(format *piazza.JsonPagination, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.FilterByMatchAll(db.mapping, format)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetAll failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetAll failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	for i, trigger := range triggers {
		triggers[i].Condition = decodeCondition(trigger.Condition).(map[string]interface{})
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) GetTriggersByDslQuery(dslString string, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.SearchByJSON(db.mapping, dslString)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByDslQuery failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByDslQuery failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) GetOne(id piazza.Ident, actor string) (*Trigger, bool, error) {
	getResult, err := db.Esi.GetByID(db.mapping, id.String())
	if err != nil {
		return nil, false, LoggedError("TriggerDB.GetOne failed: %s", err)
	}
	if getResult == nil {
		return nil, false, LoggedError("TriggerDB.GetOne failed: no getResult")
	}

	src := getResult.Source
	var trigger Trigger
	if err = json.Unmarshal(*src, &trigger); err != nil {
		return nil, getResult.Found, LoggedError("TriggerDB.GetOne failed: %s", err)
	}

	trigger.Condition = decodeCondition(trigger.Condition).(map[string]interface{})

	return &trigger, getResult.Found, nil
}

func (db *TriggerDB) GetTriggersByEventTypeID(format *piazza.JsonPagination, id piazza.Ident, actor string) ([]Trigger, int64, error) {
	triggers := []Trigger{}

	exists, err := db.Esi.TypeExists(db.mapping)
	if err != nil {
		return triggers, 0, err
	}
	if !exists {
		return triggers, 0, nil
	}

	searchResult, err := db.Esi.FilterByTermQuery(db.mapping, "eventTypeId", id, format)
	if err != nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByEventTypeId failed: %s", err)
	}
	if searchResult == nil {
		return nil, 0, LoggedError("TriggerDB.GetTriggersByEventTypeId failed: no searchResult")
	}

	if searchResult != nil && searchResult.GetHits() != nil {
		for _, hit := range *searchResult.GetHits() {
			var trigger Trigger
			if err := json.Unmarshal(*hit.Source, &trigger); err != nil {
				return nil, 0, err
			}
			triggers = append(triggers, trigger)
		}
	}
	return triggers, searchResult.TotalHits(), nil
}

func (db *TriggerDB) DeleteTrigger(id piazza.Ident, actor string) (bool, error) {
	trigger, found, err := db.GetOne(id, actor)
	if err != nil {
		return found, err
	}
	if trigger == nil {
		return false, nil
	}

	deleteResult, err := db.Esi.DeleteByID(db.mapping, string(id))
	if err != nil {
		return deleteResult.Found, LoggedError("TriggerDB.DeleteById failed: %s", err)
	}
	if deleteResult == nil {
		return false, LoggedError("TriggerDB.DeleteById failed: no deleteResult")
	}
	if !deleteResult.Found {
		return false, nil
	}

	deleteResult2, err := db.service.eventDB.Esi.DeletePercolationQuery(string(trigger.PercolationID))
	if err != nil {
		return deleteResult2.Found, LoggedError("TriggerDB.DeleteById percquery failed: %s", err)
	}
	if deleteResult2 == nil {
		return false, LoggedError("TriggerDB.DeleteById percquery failed: no deleteResult")
	}

	return deleteResult2.Found, nil
}

func encodeCondition(in interface{}) interface{} {
	return handleDotTilde(in, func(in string) string { return strings.Replace(in, ".", "~", -1) })

}
func decodeCondition(in interface{}) interface{} {
	in = handleDotTilde(in, func(in string) string { return strings.Replace(in, "~", ".", -1) })
	return handleUniqueParams(in, "unusedEventTypeName", func(eventTypeName string, key string) string {
		firstPart := key[:strings.Index(key, ".")+1]
		rest := key[strings.Index(key, ".")+1:]
		return firstPart + rest[strings.Index(rest, ".")+1:]
	})
}

func handleDotTilde(input interface{}, replace func(string) string) interface{} {
	var output interface{}
	switch input.(type) {
	case map[string]interface{}:
		output = handleDotTildeMap(input.(map[string]interface{}), replace)
	case []interface{}:
		output = handleDotTildeArr(input.([]interface{}), replace)
	}
	return output
}
func handleDotTildeMap(inputObj map[string]interface{}, replace func(string) string) map[string]interface{} {
	outputObj := map[string]interface{}{}
	for k, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj[k] = handleDotTildeArr(v.([]interface{}), replace)
		case map[string]interface{}:
			outputObj[k] = handleDotTildeMap(v.(map[string]interface{}), replace)
		default:
			outputObj[replace(k)] = v
		}
	}
	return outputObj
}
func handleDotTildeArr(inputObj []interface{}, replace func(string) string) []interface{} {
	outputObj := []interface{}{}
	for _, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj = append(outputObj, handleDotTildeArr(v.([]interface{}), replace))
		case map[string]interface{}:
			outputObj = append(outputObj, handleDotTildeMap(v.(map[string]interface{}), replace))
		default:
			outputObj = append(outputObj, v)
		}
	}
	return outputObj
}

func handleUniqueParams(input interface{}, eventTypeName string, getKey func(string, string) string) interface{} {
	var output interface{}
	switch input.(type) {
	case map[string]interface{}:
		output = handleUniqueParamsMap(input.(map[string]interface{}), eventTypeName, getKey)
	case []interface{}:
		output = handleUniqueParamsArr(input.([]interface{}), eventTypeName, getKey)
	}
	return output
}

func handleUniqueParamsMap(inputObj map[string]interface{}, eventTypeName string, getKey func(string, string) string) map[string]interface{} {
	outputObj := map[string]interface{}{}
	for k, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj[k] = handleUniqueParamsArr(v.([]interface{}), eventTypeName, getKey)
		case map[string]interface{}:
			outputObj[k] = handleUniqueParamsMap(v.(map[string]interface{}), eventTypeName, getKey)
		default:
			outputObj[getKey(eventTypeName, k)] = v
		}
	}
	return outputObj
}

func handleUniqueParamsArr(inputObj []interface{}, eventTypeName string, getKey func(string, string) string) []interface{} {
	outputObj := []interface{}{}
	for _, v := range inputObj {
		switch v.(type) {
		case []interface{}:
			outputObj = append(outputObj, handleUniqueParamsArr(v.([]interface{}), eventTypeName, getKey))
		case map[string]interface{}:
			outputObj = append(outputObj, handleUniqueParamsMap(v.(map[string]interface{}), eventTypeName, getKey))
		default:
			outputObj = append(outputObj, v)
		}
	}
	return outputObj
}
<|endoftext|>


<commit_before>package write_test

import (
	"bytes"
	"context"
	"io"
	"strings"
	"testing"

	"github.com/pkg/diff/ctxt"
	"github.com/pkg/diff/myers"
	"github.com/pkg/diff/write"
	"github.com/sergi/go-diff/diffmatchpatch"
)

var goldenTests = []struct {
	name string
	a, b string
	opts []write.Option
	want string // usually from running diff --unified and cleaning up the output
}{
	{
		name: "AddedLinesEnd",
		a:    "A\nB\nC\nD\nE\nF\n",
		b:    "A\nB\nC\nD\nE\nF\n1\n2\n3\n",
		// TODO: stock macOS diff omits the trailing common blank line in this diff,
		// which also changes the @@ line ranges to be 4,3 and 4,6.
		want: `
--- a
+++ b
@@ -4,4 +4,7 @@
 D
 E
 F
+1
+2
+3

`[1:],
	},

	{
		name: "AddedLinesStart",
		a:    "A\nB\nC\nD\nE\nF\n",
		b:    "1\n2\n3\nA\nB\nC\nD\nE\nF\n",
		want: `
--- a
+++ b
@@ -1,3 +1,6 @@
+1
+2
+3
 A
 B
 C
`[1:],
	},

	{
		name: "WithTerminalColor",
		a:    "1\n2\n2",
		b:    "1\n3\n3",
		opts: []write.Option{write.TerminalColor()},
		want: `
`[1:] + "\u001b[1m" + `--- a
+++ b
` + "\u001b[0m" + "\u001b[36m" + `@@ -1,3 +1,3 @@
` + "\u001b[0m" + ` 1
` + "\u001b[31m" + `-2
-2
` + "\u001b[32m" + `+3
+3
` + "\u001b[0m",
	},
}

func TestGolden(t *testing.T) {
	for _, test := range goldenTests {
		t.Run(test.name, func(t *testing.T) {
			as := strings.Split(test.a, "\n")
			bs := strings.Split(test.b, "\n")
			ab := &diffStrings{a: as, b: bs}
			// TODO: supply an edit.Script to the tests instead doing a Myers diff here.
			// Doing it as I have done, the lazy way, mixes concerns: diff algorithm vs unification algorithm
			// vs unified diff formatting.
			e := myers.Diff(context.Background(), ab)
			e = ctxt.Size(e, 3)
			buf := new(bytes.Buffer)
			write.Unified(e, buf, ab, test.opts...)
			got := buf.String()
			if test.want != got {
				t.Logf("%q\n", test.want)
				t.Logf("%q\n", got)
				dmp := diffmatchpatch.New()
				delta := dmp.DiffMain(test.want, got, false)
				t.Errorf("bad diff: a=%q b=%q\n\ngot:\n%s\nwant:\n%s\ndiff:\n%s\n",
					test.a, test.b,
					got, test.want,
					dmp.DiffPrettyText(delta),
				)
			}
		})
	}
}

type diffStrings struct {
	a, b []string
}

func (ab *diffStrings) LenA() int                                { return len(ab.a) }
func (ab *diffStrings) LenB() int                                { return len(ab.b) }
func (ab *diffStrings) Equal(ai, bi int) bool                    { return ab.a[ai] == ab.b[bi] }
func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.a[i]) }
func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.b[i]) }
<commit_msg>write: check err from Unified in test<commit_after>package write_test

import (
	"bytes"
	"context"
	"io"
	"strings"
	"testing"

	"github.com/pkg/diff/ctxt"
	"github.com/pkg/diff/myers"
	"github.com/pkg/diff/write"
	"github.com/sergi/go-diff/diffmatchpatch"
)

var goldenTests = []struct {
	name string
	a, b string
	opts []write.Option
	want string // usually from running diff --unified and cleaning up the output
}{
	{
		name: "AddedLinesEnd",
		a:    "A\nB\nC\nD\nE\nF\n",
		b:    "A\nB\nC\nD\nE\nF\n1\n2\n3\n",
		// TODO: stock macOS diff omits the trailing common blank line in this diff,
		// which also changes the @@ line ranges to be 4,3 and 4,6.
		want: `
--- a
+++ b
@@ -4,4 +4,7 @@
 D
 E
 F
+1
+2
+3

`[1:],
	},

	{
		name: "AddedLinesStart",
		a:    "A\nB\nC\nD\nE\nF\n",
		b:    "1\n2\n3\nA\nB\nC\nD\nE\nF\n",
		want: `
--- a
+++ b
@@ -1,3 +1,6 @@
+1
+2
+3
 A
 B
 C
`[1:],
	},

	{
		name: "WithTerminalColor",
		a:    "1\n2\n2",
		b:    "1\n3\n3",
		opts: []write.Option{write.TerminalColor()},
		want: `
`[1:] + "\u001b[1m" + `--- a
+++ b
` + "\u001b[0m" + "\u001b[36m" + `@@ -1,3 +1,3 @@
` + "\u001b[0m" + ` 1
` + "\u001b[31m" + `-2
-2
` + "\u001b[32m" + `+3
+3
` + "\u001b[0m",
	},
}

func TestGolden(t *testing.T) {
	for _, test := range goldenTests {
		t.Run(test.name, func(t *testing.T) {
			as := strings.Split(test.a, "\n")
			bs := strings.Split(test.b, "\n")
			ab := &diffStrings{a: as, b: bs}
			// TODO: supply an edit.Script to the tests instead doing a Myers diff here.
			// Doing it as I have done, the lazy way, mixes concerns: diff algorithm vs unification algorithm
			// vs unified diff formatting.
			e := myers.Diff(context.Background(), ab)
			e = ctxt.Size(e, 3)
			buf := new(bytes.Buffer)
			_, err := write.Unified(e, buf, ab, test.opts...)
			if err != nil {
				t.Fatal(err)
			}
			got := buf.String()
			if test.want != got {
				t.Logf("%q\n", test.want)
				t.Logf("%q\n", got)
				dmp := diffmatchpatch.New()
				delta := dmp.DiffMain(test.want, got, false)
				t.Errorf("bad diff: a=%q b=%q\n\ngot:\n%s\nwant:\n%s\ndiff:\n%s\n",
					test.a, test.b,
					got, test.want,
					dmp.DiffPrettyText(delta),
				)
			}
		})
	}
}

type diffStrings struct {
	a, b []string
}

func (ab *diffStrings) LenA() int                                { return len(ab.a) }
func (ab *diffStrings) LenB() int                                { return len(ab.b) }
func (ab *diffStrings) Equal(ai, bi int) bool                    { return ab.a[ai] == ab.b[bi] }
func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.a[i]) }
func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.b[i]) }
<|endoftext|>


<commit_before>package jcapi

import (
	"encoding/json"
	"fmt"
)

type JCRadiusServer struct {
	Id              string   `json:"_id,omitempty"`
	Name            string   `json:"name,omitempty"`
	NetworkSourceIP string   `json:"networkSourceIp,omitempty"`
	SharedSecret    string   `json:"sharedSecret,omitempty"`
	TagList         []string `json:"tags,omitempty"`
}

const (
	BAD_FIELD_NAME      = -3
	OBJECT_NOT_FOUND    = -2
	BAD_COMPARISON_TYPE = -1
)

func (e JCRadiusServer) ToString() string {
	return fmt.Sprintf("radiusserver: id='%s' - name='%s' - IP='%s' - Secret='%s' - TagList=%s",
		e.Id, e.Name, e.NetworkSourceIP, e.SharedSecret, e.TagList)
}

func getJCRadiusServerFieldsFromInterface(fields map[string]interface{}, e *JCRadiusServer) {
	e.Id = fields["_id"].(string)

	e.Name = fields["name"].(string)

	if _, exists := fields["networkSourceIp"]; exists {
		e.NetworkSourceIP = fields["networkSourceIp"].(string)
	}
	if _, exists := fields["sharedSecret"]; exists {
		e.SharedSecret = fields["sharedSecret"].(string)
	}
	if _, exists := fields["tags"]; exists {
		for i, _ := range fields["tags"].([]interface{}) {
			e.TagList = append(e.TagList, (fields["tags"].([]interface{}))[i].(string))
		}
	}
}

func getJCRadiusServerFromInterface(radiusServer interface{}) []JCRadiusServer {

	var returnVal []JCRadiusServer

	recMap := radiusServer.(map[string]interface{})

	results := recMap["results"].([]interface{})

	returnVal = make([]JCRadiusServer, len(results))

	for idx, result := range results {
		getJCRadiusServerFieldsFromInterface(result.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

func (jc JCAPI) GetAllRadiusServers() (radiusServers []JCRadiusServer, err JCError) {

	result, err := jc.Get("/radiusservers")
	if err != nil {
		err = fmt.Errorf("ERROR: Could not list RADIUS servers, err='%s'", err)
		return
	}

	radiusServers = getJCRadiusServerFromInterface(result)

	return
}

func FindRadiusServerById(radiusServers []JCRadiusServer, id string) (radiusServer *JCRadiusServer) {
	index := FindObject(GetInterfaceArrayFromJCRadiusServer(radiusServers), "Id", id)
	if index >= 0 {
		radiusServer = &radiusServers[index]
	}

	return
}

func GetInterfaceArrayFromJCRadiusServer(radiusServers []JCRadiusServer) (interfaceArray []interface{}) {
	interfaceArray = make([]interface{}, len(radiusServers))

	for i := range radiusServers {
		interfaceArray[i] = radiusServers[i]
	}

	return
}

//
// Add or Update a radiusserver in place on JumpCloud
//
func (jc JCAPI) AddUpdateRadiusServer(op JCOp, radiusServer JCRadiusServer) (id string, err JCError) {
	data, err := json.Marshal(radiusServer)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not marshal JCRadius object, err='%s'", err)
	}

	//fmt.Printf("op='%s', data='%s'\n", MapJCOpToHTTP(op), string(data))

	url := "/radiusservers"
	if op == Update {
		url += "/" + radiusServer.Id
	}

	radiusServerRec, err := jc.Do(MapJCOpToHTTP(op), url, data)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not post new JCIDSource object, err='%s'", err)
	}

	var resultES JCRadiusServer
	getJCRadiusServerFieldsFromInterface(radiusServerRec.(map[string]interface{}), &resultES)

	if resultES.Name != radiusServer.Name {
		return "", fmt.Errorf("ERROR: JumpCloud did not return the same ID source name - this should never happen!")
	}

	return resultES.Id, nil
}

func (jc JCAPI) DeleteRadiusServer(radiusServer JCRadiusServer) JCError {
	_, err := jc.Delete(fmt.Sprintf("/radiusservers/%s", radiusServer.Id))
	if err != nil {
		return fmt.Errorf("ERROR: Could not delete ID source ID '%s': err='%s'", radiusServer.Id, err)
	}

	return nil
}
<commit_msg>Removed commented-out debug print<commit_after>package jcapi

import (
	"encoding/json"
	"fmt"
)

type JCRadiusServer struct {
	Id              string   `json:"_id,omitempty"`
	Name            string   `json:"name,omitempty"`
	NetworkSourceIP string   `json:"networkSourceIp,omitempty"`
	SharedSecret    string   `json:"sharedSecret,omitempty"`
	TagList         []string `json:"tags,omitempty"`
}

const (
	BAD_FIELD_NAME      = -3
	OBJECT_NOT_FOUND    = -2
	BAD_COMPARISON_TYPE = -1
)

func (e JCRadiusServer) ToString() string {
	return fmt.Sprintf("radiusserver: id='%s' - name='%s' - IP='%s' - Secret='%s' - TagList=%s",
		e.Id, e.Name, e.NetworkSourceIP, e.SharedSecret, e.TagList)
}

func getJCRadiusServerFieldsFromInterface(fields map[string]interface{}, e *JCRadiusServer) {
	e.Id = fields["_id"].(string)

	e.Name = fields["name"].(string)

	if _, exists := fields["networkSourceIp"]; exists {
		e.NetworkSourceIP = fields["networkSourceIp"].(string)
	}
	if _, exists := fields["sharedSecret"]; exists {
		e.SharedSecret = fields["sharedSecret"].(string)
	}
	if _, exists := fields["tags"]; exists {
		for i, _ := range fields["tags"].([]interface{}) {
			e.TagList = append(e.TagList, (fields["tags"].([]interface{}))[i].(string))
		}
	}
}

func getJCRadiusServerFromInterface(radiusServer interface{}) []JCRadiusServer {

	var returnVal []JCRadiusServer

	recMap := radiusServer.(map[string]interface{})

	results := recMap["results"].([]interface{})

	returnVal = make([]JCRadiusServer, len(results))

	for idx, result := range results {
		getJCRadiusServerFieldsFromInterface(result.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

func (jc JCAPI) GetAllRadiusServers() (radiusServers []JCRadiusServer, err JCError) {

	result, err := jc.Get("/radiusservers")
	if err != nil {
		err = fmt.Errorf("ERROR: Could not list RADIUS servers, err='%s'", err)
		return
	}

	radiusServers = getJCRadiusServerFromInterface(result)

	return
}

func FindRadiusServerById(radiusServers []JCRadiusServer, id string) (radiusServer *JCRadiusServer) {
	index := FindObject(GetInterfaceArrayFromJCRadiusServer(radiusServers), "Id", id)
	if index >= 0 {
		radiusServer = &radiusServers[index]
	}

	return
}

func GetInterfaceArrayFromJCRadiusServer(radiusServers []JCRadiusServer) (interfaceArray []interface{}) {
	interfaceArray = make([]interface{}, len(radiusServers))

	for i := range radiusServers {
		interfaceArray[i] = radiusServers[i]
	}

	return
}

//
// Add or Update a radiusserver in place on JumpCloud
//
func (jc JCAPI) AddUpdateRadiusServer(op JCOp, radiusServer JCRadiusServer) (id string, err JCError) {
	data, err := json.Marshal(radiusServer)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not marshal JCRadius object, err='%s'", err)
	}

	url := "/radiusservers"
	if op == Update {
		url += "/" + radiusServer.Id
	}

	radiusServerRec, err := jc.Do(MapJCOpToHTTP(op), url, data)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not post new JCIDSource object, err='%s'", err)
	}

	var resultES JCRadiusServer
	getJCRadiusServerFieldsFromInterface(radiusServerRec.(map[string]interface{}), &resultES)

	if resultES.Name != radiusServer.Name {
		return "", fmt.Errorf("ERROR: JumpCloud did not return the same ID source name - this should never happen!")
	}

	return resultES.Id, nil
}

func (jc JCAPI) DeleteRadiusServer(radiusServer JCRadiusServer) JCError {
	_, err := jc.Delete(fmt.Sprintf("/radiusservers/%s", radiusServer.Id))
	if err != nil {
		return fmt.Errorf("ERROR: Could not delete ID source ID '%s': err='%s'", radiusServer.Id, err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017-present Kirill Danshin and Gramework contributors
// Copyright 2019-present Highload LTD (UK CN: 11893420)
//
// 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
//

package client

import (
	"time"

	"github.com/fasthttp/websocket"
	"github.com/gramework/gramework"
)

// WSHandler returns gramework handler
func (client *Instance) WSHandler() func(*gramework.Context) error {
	return func(ctx *gramework.Context) error {
		if websocket.IsWebSocketUpgrade(ctx.RequestCtx) {
			return websocket.Upgrade(ctx.RequestCtx, func(conn *websocket.Conn) {
				for {
					v := <-client.watch(ctx)
					conn.WriteMessage(websocket.TextMessage, v)
				}
			}, 0, 0)
		}

		return client.handleHTTP(ctx)
	}
}

func (client *Instance) watch(ctx *gramework.Context) chan []byte {
	c := make(chan []byte)
	go func() {
		for {
			api, err := client.nextServer()
			if err != nil {
				ctx.Logger.Errorf("error: %s", err)
				time.Sleep(client.conf.WatcherTickTime)
				continue
			}
			bytes := buffer.Get()
			_, body, err := api.HostClient.Get(bytes.B, api.Addr)
			if err != nil {
				ctx.Logger.Errorf("error while .Do() the request %s", err)
				time.Sleep(client.conf.WatcherTickTime)
				buffer.Put(bytes)
				continue
			}
			c <- body
			time.Sleep(client.conf.WatcherTickTime)
			buffer.Put(bytes)
		}
	}()

	return c
}
<commit_msg>x/client: fix wsHandler<commit_after>// Copyright 2017-present Kirill Danshin and Gramework contributors
// Copyright 2019-present Highload LTD (UK CN: 11893420)
//
// 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
//

package client

import (
	"time"

	"github.com/fasthttp/websocket"
	"github.com/gramework/gramework"
)

// WSHandler returns gramework handler
func (client *Instance) WSHandler() func(*gramework.Context) error {
	up := websocket.FastHTTPUpgrader{}
	return func(ctx *gramework.Context) error {
		if websocket.FastHTTPIsWebSocketUpgrade(ctx.RequestCtx) {
			return up.Upgrade(ctx.RequestCtx, func(conn *websocket.Conn) {
				for {
					v := <-client.watch(ctx)
					conn.WriteMessage(websocket.TextMessage, v)
				}
			})
		}

		return client.handleHTTP(ctx)
	}
}

func (client *Instance) watch(ctx *gramework.Context) chan []byte {
	c := make(chan []byte)
	go func() {
		for {
			api, err := client.nextServer()
			if err != nil {
				ctx.Logger.Errorf("error: %s", err)
				time.Sleep(client.conf.WatcherTickTime)
				continue
			}
			bytes := buffer.Get()
			_, body, err := api.HostClient.Get(bytes.B, api.Addr)
			if err != nil {
				ctx.Logger.Errorf("error while .Do() the request %s", err)
				time.Sleep(client.conf.WatcherTickTime)
				buffer.Put(bytes)
				continue
			}
			c <- body
			time.Sleep(client.conf.WatcherTickTime)
			buffer.Put(bytes)
		}
	}()

	return c
}
<|endoftext|>


<commit_before>package prompts

import (
	"fmt"
	"text/template"

	"github.com/manifoldco/promptui"
	money "github.com/rhymond/go-money"
)

const (
	active   = `▸ {{.Body.Name | bold}} ({{ .Body.Label | blue }})`
	inactive = `  {{.Body.Name }} ({{ .Body.Label | blue }})`
	selected = `{{"✔" | green }} %s: {{.Body.Name | bold}} ({{ .Body.Label | blue }})`
)

var funcMap template.FuncMap

func init() {
	funcMap = promptui.FuncMap
	funcMap["price"] = price
}

var ProductSelect = &promptui.SelectTemplates{
	Active:   active,
	Inactive: inactive,
	Selected: fmt.Sprintf(selected, "Product"),
	Details: `
{{ .Body.Name | bold }}:
{{ .Body.Tagline }}
{{- range $i, $el := .Body.ValueProps }}
{{- if lt $i 3 }}
 - {{ $el.Header -}}
{{- end -}}
{{- end -}}`,
}

var PlanSelect = &promptui.SelectTemplates{
	FuncMap:  funcMap,
	Active:   active,
	Inactive: inactive,
	Selected: fmt.Sprintf(selected, "Plan"),
	Details: `
{{ .Body.Name | bold }} - {{ .Body.Cost | price }}:
{{- range $i, $el := .Body.Features }}
{{- if lt $i 3 }}
 - {{ $el.Feature }}: {{ $el.Value -}}
{{- end -}}
{{- end -}}`,
}

func price(value *int64) string {
	price := *value
	if price == 0 {
		return "Free"
	}
	return money.New(price, "USD").Display()
}
<commit_msg>wip formatting<commit_after>package prompts

import (
	"fmt"
	"strings"
	"text/template"

	"github.com/manifoldco/promptui"
	money "github.com/rhymond/go-money"
)

const (
	active   = `▸ {{.Body.Label | blue | bold }} ({{ .Body.Name }})`
	inactive = `  {{.Body.Label | blue }} ({{ .Body.Name }})`
	selected = `{{"✔" | green }} %s: {{.Body.Label | blue}} ({{ .Body.Name }})`
)

var funcMap template.FuncMap

func init() {
	funcMap = promptui.FuncMap
	funcMap["price"] = price
	funcMap["title"] = title
}

var ProductSelect = &promptui.SelectTemplates{
	Active:   active,
	Inactive: inactive,
	Selected: fmt.Sprintf(selected, "Product"),
	Details: `
Product:	{{.Body.Label | blue}} ({{ .Body.Label }})
Tagline	{{ .Body.Tagline }}
Features:
{{- range $i, $el := .Body.ValueProps }}
{{- if lt $i 3 }}
 {{ $el.Header -}}
{{- end -}}
{{- end -}}`,
}

var PlanSelect = &promptui.SelectTemplates{
	FuncMap:  funcMap,
	Active:   active,
	Inactive: inactive,
	Selected: fmt.Sprintf(selected, "Plan"),
	Details: `
Plan:	{{.Body.Label | blue}} ({{ .Body.Label }})
Price:	{{ .Body.Cost | price }}
{{- range $i, $el := .Body.Features }}
{{- if lt $i 3 }}
{{ $el.Feature | title }}:	{{ $el.Value -}}
{{- end -}}
{{- end -}}`,
}

func price(value *int64) string {
	price := *value
	if price == 0 {
		return "Free"
	}
	return money.New(price, "USD").Display() + "/month"
}

func title(v interface{}) string {
	val := fmt.Sprintf("%v", v)
	return strings.Title(val)
}
<|endoftext|>


<commit_before>package somatree

import (
	"log"
	"sync"

	"github.com/satori/go.uuid"
)

// Implementation of the `Propertier` interface

//
// Propertier:> Add Property

func (teb *SomaTreeElemBucket) SetProperty(p Property) {
	// if deleteOK is true, then prop is the property that can be
	// deleted
	if dupe, deleteOK, _ := teb.checkDuplicate(p); dupe && !deleteOK {
		log.Printf("bucket.SetProperty() detected hard duplicate")
		return // TODO: error out via FaultElement
	} else if dupe && deleteOK {
		// TODO delete inherited value
		// teb.DelProperty(prop)
		log.Printf("bucket.SetProperty() detected soft duplicate")
		return
	}
	p.SetId(p.GetInstanceId(teb.Type, teb.Id))
	if p.Equal(uuid.Nil) {
		p.SetId(uuid.NewV4())
	}
	log.Printf("SetProperty(Bucket) created source instance: %s", p.GetID())
	// this property is the source instance
	p.SetInheritedFrom(teb.Id)
	p.SetInherited(false)
	p.SetSourceType(teb.Type)
	if i, e := uuid.FromString(p.GetID()); e == nil {
		p.SetSourceId(i)
	}
	// send a scrubbed copy down
	f := p.Clone()
	f.SetInherited(true)
	f.SetId(uuid.UUID{})
	teb.setPropertyOnChildren(f)
	// scrub instance startup information prior to storing
	p.clearInstances()
	teb.addProperty(p)
	teb.actionPropertyNew(p.MakeAction())
}

func (teb *SomaTreeElemBucket) setPropertyInherited(p Property) {
	f := p.Clone()
	f.SetId(f.GetInstanceId(teb.Type, teb.Id))
	if f.Equal(uuid.Nil) {
		f.SetId(uuid.NewV4())
		log.Printf("Inherit (Bucket) Generated: %s", f.GetID())
	}
	f.clearInstances()

	teb.addProperty(f)
	p.SetId(uuid.UUID{})
	teb.setPropertyOnChildren(p)
	teb.actionPropertyNew(f.MakeAction())
}

func (teb *SomaTreeElemBucket) setPropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	log.Printf("InheritDeep Sending down: %s", p.GetID())
	for child, _ := range teb.Children {
		wg.Add(1)
		c := child
		go func(stp Property) {
			defer wg.Done()
			teb.Children[c].setPropertyInherited(stp)
		}(p)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) addProperty(p Property) {
	switch p.GetType() {
	case `custom`:
		teb.PropertyCustom[p.GetID()] = p
	case `system`:
		teb.PropertyService[p.GetID()] = p
	case `service`:
		teb.PropertySystem[p.GetID()] = p
	case `oncall`:
		teb.PropertyOncall[p.GetID()] = p
	}
}

//
// Propertier:> Update Property

func (teb *SomaTreeElemBucket) UpdateProperty(p Property) {
	if !teb.verifySourceInstance(
		p.GetSourceInstance(),
		p.GetType(),
	) {
		return // XXX faultChannel
	}

	// keep a copy for ourselves, no shared pointers
	f := p.Clone()
	teb.switchProperty(f)
	teb.updatePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) updatePropertyInherited(p Property) {
	// keep a copy for ourselves, no shared pointers
	f := p.Clone()
	teb.switchProperty(f)
	teb.updatePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) updatePropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	for child, _ := range teb.Children {
		wg.Add(1)
		go func(stp Property, c string) {
			defer wg.Done()
			teb.Children[c].updatePropertyInherited(stp)
		}(p, child)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) switchProperty(p Property) {
	updId, _ := uuid.FromString(teb.findIdForSource(
		p.GetSourceInstance(),
		p.GetType(),
	))
	p.SetId(updId)
	teb.addProperty(p)
	teb.actionPropertyUpdate(p.MakeAction())
}

//
// Propertier:> Delete Property

func (teb *SomaTreeElemBucket) DeleteProperty(p Property) {
	if !teb.verifySourceInstance(
		p.GetSourceInstance(),
		p.GetType(),
	) {
		return // XXX faultChannel
	}

	teb.rmProperty(p)
	teb.deletePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) deletePropertyInherited(p Property) {
	teb.rmProperty(p)
	teb.deletePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) deletePropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	for child, _ := range teb.Children {
		wg.Add(1)
		go func(stp Property, c string) {
			defer wg.Done()
			teb.Children[c].deletePropertyInherited(stp)
		}(p, child)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) rmProperty(p Property) {
	delId, _ := uuid.FromString(teb.findIdForSource(
		p.GetSourceInstance(),
		p.GetType(),
	))
	p.SetId(delId)
	teb.actionPropertyDelete(p.MakeAction())

	switch p.GetType() {
	case `custom`:
		delete(teb.PropertyCustom, delId.String())
	case `service`:
		delete(teb.PropertyService, delId.String())
	case `system`:
		delete(teb.PropertySystem, delId.String())
	case `oncall`:
		delete(teb.PropertyOncall, delId.String())
	}
}

//
// Propertier:> Utility

// used to verify this is a source instance
func (teb *SomaTreeElemBucket) verifySourceInstance(id, prop string) bool {
	switch prop {
	case `custom`:
		if _, ok := teb.PropertyCustom[id]; !ok {
			return false
		}
		return teb.PropertyCustom[id].GetSourceInstance() == id
	case `service`:
		if _, ok := teb.PropertyService[id]; !ok {
			return false
		}
		return teb.PropertyService[id].GetSourceInstance() == id
	case `system`:
		if _, ok := teb.PropertySystem[id]; !ok {
			return false
		}
		return teb.PropertySystem[id].GetSourceInstance() == id
	case `oncall`:
		if _, ok := teb.PropertyOncall[id]; !ok {
			return false
		}
		return teb.PropertyOncall[id].GetSourceInstance() == id
	default:
		return false
	}
}

//
func (teb *SomaTreeElemBucket) findIdForSource(source, prop string) string {
	switch prop {
	case `custom`:
		for id, _ := range teb.PropertyCustom {
			if teb.PropertyCustom[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `system`:
		for id, _ := range teb.PropertyService {
			if teb.PropertyService[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `service`:
		for id, _ := range teb.PropertySystem {
			if teb.PropertySystem[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `oncall`:
		for id, _ := range teb.PropertyOncall {
			if teb.PropertyOncall[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	}
	return ``
}

// when a child attaches, it calls self.Parent.syncProperty(self.Id)
// to get get all properties of that part of the tree
func (teb *SomaTreeElemBucket) syncProperty(childId string) {
customloop:
	for prop, _ := range teb.PropertyCustom {
		if !teb.PropertyCustom[prop].hasInheritance() {
			continue customloop
		}
		f := teb.PropertyCustom[prop].(*PropertyCustom).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
oncallloop:
	for prop, _ := range teb.PropertyOncall {
		if !teb.PropertyOncall[prop].hasInheritance() {
			continue oncallloop
		}
		f := teb.PropertyOncall[prop].(*PropertyOncall).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
serviceloop:
	for prop, _ := range teb.PropertyService {
		if !teb.PropertyService[prop].hasInheritance() {
			continue serviceloop
		}
		f := teb.PropertyService[prop].(*PropertyService).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
systemloop:
	for prop, _ := range teb.PropertySystem {
		if !teb.PropertySystem[prop].hasInheritance() {
			continue systemloop
		}
		f := teb.PropertySystem[prop].(*PropertySystem).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
}

// function to be used by a child to check if the parent has a
// specific Property
func (teb *SomaTreeElemBucket) checkProperty(propType string, propId string) bool {
	switch propType {
	case "custom":
		if _, ok := teb.PropertyCustom[propId]; ok {
			return true
		}
	case "service":
		if _, ok := teb.PropertyService[propId]; ok {
			return true
		}
	case "system":
		if _, ok := teb.PropertySystem[propId]; ok {
			return true
		}
	case "oncall":
		if _, ok := teb.PropertyOncall[propId]; ok {
			return true
		}
	}
	return false
}

// Checks if this property is already defined on this node, and
// whether it was inherited, ie. can be deleted so it can be
// overwritten
func (teb *SomaTreeElemBucket) checkDuplicate(p Property) (bool, bool, Property) {
	var dupe, deleteOK bool
	var prop Property

propswitch:
	switch p.GetType() {
	case "custom":
		for _, pVal := range teb.PropertyCustom {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "service":
		for _, pVal := range teb.PropertyService {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "oncall":
		for _, pVal := range teb.PropertyOncall {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "system":
		for _, pVal := range teb.PropertySystem {
			// tags are only dupes if the value is the same as well
			if p.GetKey() != `tag` {
				dupe, deleteOK, prop = isDupe(pVal, p)
				if dupe {
					break propswitch
				}
			} else if p.GetValue() == pVal.GetValue() {
				// tag and same value, can be a dupe
				dupe, deleteOK, prop = isDupe(pVal, p)
				if dupe {
					break propswitch
				}
			}
			// tag + different value => pass
		}
	default:
		// trigger error path
		dupe = true
		deleteOK = false
	}
	return dupe, deleteOK, prop
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Fix function call mixup<commit_after>package somatree

import (
	"log"
	"sync"

	"github.com/satori/go.uuid"
)

// Implementation of the `Propertier` interface

//
// Propertier:> Add Property

func (teb *SomaTreeElemBucket) SetProperty(p Property) {
	// if deleteOK is true, then prop is the property that can be
	// deleted
	if dupe, deleteOK, _ := teb.checkDuplicate(p); dupe && !deleteOK {
		log.Printf("bucket.SetProperty() detected hard duplicate")
		return // TODO: error out via FaultElement
	} else if dupe && deleteOK {
		// TODO delete inherited value
		// teb.DelProperty(prop)
		log.Printf("bucket.SetProperty() detected soft duplicate")
		return
	}
	p.SetId(p.GetInstanceId(teb.Type, teb.Id))
	if p.Equal(uuid.Nil) {
		p.SetId(uuid.NewV4())
	}
	log.Printf("SetProperty(Bucket) created source instance: %s", p.GetID())
	// this property is the source instance
	p.SetInheritedFrom(teb.Id)
	p.SetInherited(false)
	p.SetSourceType(teb.Type)
	if i, e := uuid.FromString(p.GetID()); e == nil {
		p.SetSourceId(i)
	}
	// send a scrubbed copy down
	f := p.Clone()
	f.SetInherited(true)
	f.SetId(uuid.UUID{})
	teb.setPropertyOnChildren(f)
	// scrub instance startup information prior to storing
	p.clearInstances()
	teb.addProperty(p)
	teb.actionPropertyNew(p.MakeAction())
}

func (teb *SomaTreeElemBucket) setPropertyInherited(p Property) {
	f := p.Clone()
	f.SetId(f.GetInstanceId(teb.Type, teb.Id))
	if f.Equal(uuid.Nil) {
		f.SetId(uuid.NewV4())
		log.Printf("Inherit (Bucket) Generated: %s", f.GetID())
	}
	f.clearInstances()

	teb.addProperty(f)
	p.SetId(uuid.UUID{})
	teb.setPropertyOnChildren(p)
	teb.actionPropertyNew(f.MakeAction())
}

func (teb *SomaTreeElemBucket) setPropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	log.Printf("InheritDeep Sending down: %s", p.GetID())
	for child, _ := range teb.Children {
		wg.Add(1)
		c := child
		go func(stp Property) {
			defer wg.Done()
			teb.Children[c].setPropertyInherited(stp)
		}(p)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) addProperty(p Property) {
	switch p.GetType() {
	case `custom`:
		teb.PropertyCustom[p.GetID()] = p
	case `system`:
		teb.PropertySystem[p.GetID()] = p
	case `service`:
		teb.PropertyService[p.GetID()] = p
	case `oncall`:
		teb.PropertyOncall[p.GetID()] = p
	}
}

//
// Propertier:> Update Property

func (teb *SomaTreeElemBucket) UpdateProperty(p Property) {
	if !teb.verifySourceInstance(
		p.GetSourceInstance(),
		p.GetType(),
	) {
		return // XXX faultChannel
	}

	// keep a copy for ourselves, no shared pointers
	f := p.Clone()
	teb.switchProperty(f)
	teb.updatePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) updatePropertyInherited(p Property) {
	// keep a copy for ourselves, no shared pointers
	f := p.Clone()
	teb.switchProperty(f)
	teb.updatePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) updatePropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	for child, _ := range teb.Children {
		wg.Add(1)
		go func(stp Property, c string) {
			defer wg.Done()
			teb.Children[c].updatePropertyInherited(stp)
		}(p, child)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) switchProperty(p Property) {
	updId, _ := uuid.FromString(teb.findIdForSource(
		p.GetSourceInstance(),
		p.GetType(),
	))
	p.SetId(updId)
	teb.addProperty(p)
	teb.actionPropertyUpdate(p.MakeAction())
}

//
// Propertier:> Delete Property

func (teb *SomaTreeElemBucket) DeleteProperty(p Property) {
	if !teb.verifySourceInstance(
		p.GetSourceInstance(),
		p.GetType(),
	) {
		return // XXX faultChannel
	}

	teb.rmProperty(p)
	teb.deletePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) deletePropertyInherited(p Property) {
	teb.rmProperty(p)
	teb.deletePropertyOnChildren(p)
}

func (teb *SomaTreeElemBucket) deletePropertyOnChildren(p Property) {
	var wg sync.WaitGroup
	for child, _ := range teb.Children {
		wg.Add(1)
		go func(stp Property, c string) {
			defer wg.Done()
			teb.Children[c].deletePropertyInherited(stp)
		}(p, child)
	}
	wg.Wait()
}

func (teb *SomaTreeElemBucket) rmProperty(p Property) {
	delId, _ := uuid.FromString(teb.findIdForSource(
		p.GetSourceInstance(),
		p.GetType(),
	))
	p.SetId(delId)
	teb.actionPropertyDelete(p.MakeAction())

	switch p.GetType() {
	case `custom`:
		delete(teb.PropertyCustom, delId.String())
	case `service`:
		delete(teb.PropertyService, delId.String())
	case `system`:
		delete(teb.PropertySystem, delId.String())
	case `oncall`:
		delete(teb.PropertyOncall, delId.String())
	}
}

//
// Propertier:> Utility

// used to verify this is a source instance
func (teb *SomaTreeElemBucket) verifySourceInstance(id, prop string) bool {
	switch prop {
	case `custom`:
		if _, ok := teb.PropertyCustom[id]; !ok {
			return false
		}
		return teb.PropertyCustom[id].GetSourceInstance() == id
	case `service`:
		if _, ok := teb.PropertyService[id]; !ok {
			return false
		}
		return teb.PropertyService[id].GetSourceInstance() == id
	case `system`:
		if _, ok := teb.PropertySystem[id]; !ok {
			return false
		}
		return teb.PropertySystem[id].GetSourceInstance() == id
	case `oncall`:
		if _, ok := teb.PropertyOncall[id]; !ok {
			return false
		}
		return teb.PropertyOncall[id].GetSourceInstance() == id
	default:
		return false
	}
}

//
func (teb *SomaTreeElemBucket) findIdForSource(source, prop string) string {
	switch prop {
	case `custom`:
		for id, _ := range teb.PropertyCustom {
			if teb.PropertyCustom[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `system`:
		for id, _ := range teb.PropertyService {
			if teb.PropertyService[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `service`:
		for id, _ := range teb.PropertySystem {
			if teb.PropertySystem[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	case `oncall`:
		for id, _ := range teb.PropertyOncall {
			if teb.PropertyOncall[id].GetSourceInstance() != source {
				continue
			}
			return id
		}
	}
	return ``
}

// when a child attaches, it calls self.Parent.syncProperty(self.Id)
// to get get all properties of that part of the tree
func (teb *SomaTreeElemBucket) syncProperty(childId string) {
customloop:
	for prop, _ := range teb.PropertyCustom {
		if !teb.PropertyCustom[prop].hasInheritance() {
			continue customloop
		}
		f := teb.PropertyCustom[prop].(*PropertyCustom).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
oncallloop:
	for prop, _ := range teb.PropertyOncall {
		if !teb.PropertyOncall[prop].hasInheritance() {
			continue oncallloop
		}
		f := teb.PropertyOncall[prop].(*PropertyOncall).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
serviceloop:
	for prop, _ := range teb.PropertyService {
		if !teb.PropertyService[prop].hasInheritance() {
			continue serviceloop
		}
		f := teb.PropertyService[prop].(*PropertyService).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
systemloop:
	for prop, _ := range teb.PropertySystem {
		if !teb.PropertySystem[prop].hasInheritance() {
			continue systemloop
		}
		f := teb.PropertySystem[prop].(*PropertySystem).Clone()
		f.SetInherited(true)
		f.SetId(uuid.UUID{})
		f.clearInstances()
		teb.Children[childId].setPropertyInherited(f)
	}
}

// function to be used by a child to check if the parent has a
// specific Property
func (teb *SomaTreeElemBucket) checkProperty(propType string, propId string) bool {
	switch propType {
	case "custom":
		if _, ok := teb.PropertyCustom[propId]; ok {
			return true
		}
	case "service":
		if _, ok := teb.PropertyService[propId]; ok {
			return true
		}
	case "system":
		if _, ok := teb.PropertySystem[propId]; ok {
			return true
		}
	case "oncall":
		if _, ok := teb.PropertyOncall[propId]; ok {
			return true
		}
	}
	return false
}

// Checks if this property is already defined on this node, and
// whether it was inherited, ie. can be deleted so it can be
// overwritten
func (teb *SomaTreeElemBucket) checkDuplicate(p Property) (bool, bool, Property) {
	var dupe, deleteOK bool
	var prop Property

propswitch:
	switch p.GetType() {
	case "custom":
		for _, pVal := range teb.PropertyCustom {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "service":
		for _, pVal := range teb.PropertyService {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "oncall":
		for _, pVal := range teb.PropertyOncall {
			dupe, deleteOK, prop = isDupe(pVal, p)
			if dupe {
				break propswitch
			}
		}
	case "system":
		for _, pVal := range teb.PropertySystem {
			// tags are only dupes if the value is the same as well
			if p.GetKey() != `tag` {
				dupe, deleteOK, prop = isDupe(pVal, p)
				if dupe {
					break propswitch
				}
			} else if p.GetValue() == pVal.GetValue() {
				// tag and same value, can be a dupe
				dupe, deleteOK, prop = isDupe(pVal, p)
				if dupe {
					break propswitch
				}
			}
			// tag + different value => pass
		}
	default:
		// trigger error path
		dupe = true
		deleteOK = false
	}
	return dupe, deleteOK, prop
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>
package zego

//GET /api/v2/tickets/{ticket_id}/metrics.json

type TicketMetric struct {
    Id                 int `json:"id"`
    Ticket_id          int `json:"ticket_id"`
    Replies            int `json:"replies"`
    ReplyTime          int `json:"reply_time_in_minutes.business"`
    FirstResolution	   int `json:"first_resolution_time_in_minutes.business"`
    CreatedAt          string `json:"created_at"`
}

func (a Auth) GetTicketMetrics(ticket_id int) (*Resource, error) {

    path := "/tickets/" + string(ticket_id) + "/metrics.json"
    resource, err := api(a, "GET", path, "")
    if err != nil {
        return nil, err
    }
    return resource, nil
}

<commit_msg>Revert "implemented ticket metrics API"<commit_after><|endoftext|>


<commit_before>// package idletiming provides mechanisms for adding idle timeouts to net.Conn
// and net.Listener.
package idletiming

import (
	"net"
	"sync/atomic"
	"time"
)

var (
	epoch = time.Unix(0, 0)
)

// Conn creates a new net.Conn wrapping the given net.Conn that times out after
// the specified period. Read and Write calls will timeout if they take longer
// than the indicated idleTimeout.
//
// idleTimeout specifies how long to wait for inactivity before considering
// connection idle.
//
// onClose is an optional function to call after the connection has idled or
// been closed.
//
// Note - idletiming.Conn does not close the underlying connection, it is up to
// clients to handle this in their onClose callback.
func Conn(conn net.Conn, idleTimeout time.Duration, onClose func()) *IdleTimingConn {
	c := &IdleTimingConn{
		conn:             conn,
		idleTimeout:      idleTimeout,
		halfIdleTimeout:  time.Duration(idleTimeout.Nanoseconds() / 2),
		activeCh:         make(chan bool, 1),
		closedCh:         make(chan bool, 1),
		lastActivityTime: int64(time.Now().UnixNano()),
	}

	go func() {
		if onClose != nil {
			defer onClose()
		}

		timer := time.NewTimer(idleTimeout)
		defer timer.Stop()
		for {
			select {
			case <-c.activeCh:
				// We're active, continue
				timer.Reset(idleTimeout)
				atomic.StoreInt64(&c.lastActivityTime, time.Now().UnixNano())
				continue
			case <-timer.C:
				return
			case <-c.closedCh:
				c.Close()
				return
			}
		}
	}()

	return c
}

// IdleTimingConn is a net.Conn that wraps another net.Conn and that times out
// if idle for more than idleTimeout.
type IdleTimingConn struct {
	conn             net.Conn
	idleTimeout      time.Duration
	halfIdleTimeout  time.Duration
	readDeadline     int64
	writeDeadline    int64
	activeCh         chan bool
	closedCh         chan bool
	lastActivityTime int64
}

// TimesOutIn returns how much time is left before this connection will time
// out, assuming there is no further activity.
func (c *IdleTimingConn) TimesOutIn() time.Duration {
	return c.TimesOutAt().Sub(time.Now())
}

// TimesOutAt returns the time at which this connection will time out, assuming
// there is no further activity
func (c *IdleTimingConn) TimesOutAt() time.Time {
	return time.Unix(0, c.lastActivityTime).Add(c.idleTimeout)
}

// Read implements the method from io.Reader
func (c *IdleTimingConn) Read(b []byte) (int, error) {
	totalN := 0
	readDeadline := time.Unix(0, atomic.LoadInt64(&c.readDeadline))

	// Continually read while we can, always setting a deadline that's less than
	// our idleTimeout so that we can update our active status before we hit the
	// idleTimeout.
	for {
		maxDeadline := time.Now().Add(c.halfIdleTimeout)
		if readDeadline != epoch && !maxDeadline.Before(readDeadline) {
			// Caller's deadline is before ours, use it
			c.conn.SetReadDeadline(readDeadline)
			n, err := c.conn.Read(b)
			c.markActive(n)
			totalN = totalN + n
			return totalN, err
		} else {
			// Use our own deadline
			c.conn.SetReadDeadline(maxDeadline)
			n, err := c.conn.Read(b)
			c.markActive(n)
			totalN = totalN + n
			hitMaxDeadline := isTimeout(err) && !time.Now().Before(maxDeadline)
			if hitMaxDeadline {
				// Ignore timeouts when encountering deadline based on
				// IdleTimeout
				err = nil
			}
			if n == 0 || !hitMaxDeadline {
				return totalN, err
			}
			b = b[n:]
		}
	}
}

// Write implements the method from io.Reader
func (c *IdleTimingConn) Write(b []byte) (int, error) {
	totalN := 0
	writeDeadline := time.Unix(0, atomic.LoadInt64(&c.writeDeadline))

	// Continually write while we can, always setting a deadline that's less
	// than our idleTimeout so that we can update our active status before we
	// hit the idleTimeout.
	for {
		maxDeadline := time.Now().Add(c.halfIdleTimeout)
		if writeDeadline != epoch && !maxDeadline.Before(writeDeadline) {
			// Caller's deadline is before ours, use it
			c.conn.SetWriteDeadline(writeDeadline)
			n, err := c.conn.Write(b)
			c.markActive(n)
			totalN = totalN + n
			return totalN, err
		} else {
			// Use our own deadline
			c.conn.SetWriteDeadline(maxDeadline)
			n, err := c.conn.Write(b)
			c.markActive(n)
			totalN = totalN + n
			hitMaxDeadline := isTimeout(err) && !time.Now().Before(maxDeadline)
			if hitMaxDeadline {
				// Ignore timeouts when encountering deadline based on
				// IdleTimeout
				err = nil
			}
			if n == 0 || !hitMaxDeadline {
				return totalN, err
			}
			b = b[n:]
		}
	}
}

// Close this IdleTimingConn. This will close the underlying net.Conn as well,
// however any error from closing that connection is NOT returned here.
func (c *IdleTimingConn) Close() error {
	select {
	case c.closedCh <- true:
		// close accepted
	default:
		// already closing, ignore
	}
	return c.conn.Close()
}

func (c *IdleTimingConn) LocalAddr() net.Addr {
	return c.conn.LocalAddr()
}

func (c *IdleTimingConn) RemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}

func (c *IdleTimingConn) SetDeadline(t time.Time) error {
	c.SetReadDeadline(t)
	c.SetWriteDeadline(t)
	return nil
}

func (c *IdleTimingConn) SetReadDeadline(t time.Time) error {
	atomic.StoreInt64(&c.readDeadline, t.UnixNano())
	return nil
}

func (c *IdleTimingConn) SetWriteDeadline(t time.Time) error {
	atomic.StoreInt64(&c.writeDeadline, t.UnixNano())
	return nil
}

func (c *IdleTimingConn) markActive(n int) bool {
	if n > 0 {
		c.activeCh <- true
		return true
	} else {
		return false
	}
}

func isTimeout(err error) bool {
	if netErr, ok := err.(net.Error); ok {
		return netErr.Timeout()
	}
	return false
}
<commit_msg>getlantern/lantern#2117 More close cleanup<commit_after>// package idletiming provides mechanisms for adding idle timeouts to net.Conn
// and net.Listener.
package idletiming

import (
	"net"
	"sync/atomic"
	"time"
)

var (
	epoch = time.Unix(0, 0)
)

// Conn creates a new net.Conn wrapping the given net.Conn that times out after
// the specified period. Read and Write calls will timeout if they take longer
// than the indicated idleTimeout.
//
// idleTimeout specifies how long to wait for inactivity before considering
// connection idle.
//
// onClose is an optional function to call after the connection has idled or
// been closed.
//
// Note - idletiming.Conn does not close the underlying connection, it is up to
// clients to handle this in their onClose callback.
func Conn(conn net.Conn, idleTimeout time.Duration, onClose func()) *IdleTimingConn {
	c := &IdleTimingConn{
		conn:             conn,
		idleTimeout:      idleTimeout,
		halfIdleTimeout:  time.Duration(idleTimeout.Nanoseconds() / 2),
		activeCh:         make(chan bool, 1),
		closedCh:         make(chan bool, 1),
		lastActivityTime: int64(time.Now().UnixNano()),
	}

	go func() {
		if onClose != nil {
			defer onClose()
		}

		timer := time.NewTimer(idleTimeout)
		defer timer.Stop()
		for {
			select {
			case <-c.activeCh:
				// We're active, continue
				timer.Reset(idleTimeout)
				atomic.StoreInt64(&c.lastActivityTime, time.Now().UnixNano())
				continue
			case <-timer.C:
				return
			case <-c.closedCh:
				return
			}
		}
	}()

	return c
}

// IdleTimingConn is a net.Conn that wraps another net.Conn and that times out
// if idle for more than idleTimeout.
type IdleTimingConn struct {
	conn             net.Conn
	idleTimeout      time.Duration
	halfIdleTimeout  time.Duration
	readDeadline     int64
	writeDeadline    int64
	activeCh         chan bool
	closedCh         chan bool
	lastActivityTime int64
}

// TimesOutIn returns how much time is left before this connection will time
// out, assuming there is no further activity.
func (c *IdleTimingConn) TimesOutIn() time.Duration {
	return c.TimesOutAt().Sub(time.Now())
}

// TimesOutAt returns the time at which this connection will time out, assuming
// there is no further activity
func (c *IdleTimingConn) TimesOutAt() time.Time {
	return time.Unix(0, c.lastActivityTime).Add(c.idleTimeout)
}

// Read implements the method from io.Reader
func (c *IdleTimingConn) Read(b []byte) (int, error) {
	totalN := 0
	readDeadline := time.Unix(0, atomic.LoadInt64(&c.readDeadline))

	// Continually read while we can, always setting a deadline that's less than
	// our idleTimeout so that we can update our active status before we hit the
	// idleTimeout.
	for {
		maxDeadline := time.Now().Add(c.halfIdleTimeout)
		if readDeadline != epoch && !maxDeadline.Before(readDeadline) {
			// Caller's deadline is before ours, use it
			c.conn.SetReadDeadline(readDeadline)
			n, err := c.conn.Read(b)
			c.markActive(n)
			totalN = totalN + n
			return totalN, err
		} else {
			// Use our own deadline
			c.conn.SetReadDeadline(maxDeadline)
			n, err := c.conn.Read(b)
			c.markActive(n)
			totalN = totalN + n
			hitMaxDeadline := isTimeout(err) && !time.Now().Before(maxDeadline)
			if hitMaxDeadline {
				// Ignore timeouts when encountering deadline based on
				// IdleTimeout
				err = nil
			}
			if n == 0 || !hitMaxDeadline {
				return totalN, err
			}
			b = b[n:]
		}
	}
}

// Write implements the method from io.Reader
func (c *IdleTimingConn) Write(b []byte) (int, error) {
	totalN := 0
	writeDeadline := time.Unix(0, atomic.LoadInt64(&c.writeDeadline))

	// Continually write while we can, always setting a deadline that's less
	// than our idleTimeout so that we can update our active status before we
	// hit the idleTimeout.
	for {
		maxDeadline := time.Now().Add(c.halfIdleTimeout)
		if writeDeadline != epoch && !maxDeadline.Before(writeDeadline) {
			// Caller's deadline is before ours, use it
			c.conn.SetWriteDeadline(writeDeadline)
			n, err := c.conn.Write(b)
			c.markActive(n)
			totalN = totalN + n
			return totalN, err
		} else {
			// Use our own deadline
			c.conn.SetWriteDeadline(maxDeadline)
			n, err := c.conn.Write(b)
			c.markActive(n)
			totalN = totalN + n
			hitMaxDeadline := isTimeout(err) && !time.Now().Before(maxDeadline)
			if hitMaxDeadline {
				// Ignore timeouts when encountering deadline based on
				// IdleTimeout
				err = nil
			}
			if n == 0 || !hitMaxDeadline {
				return totalN, err
			}
			b = b[n:]
		}
	}
}

// Close this IdleTimingConn. This will close the underlying net.Conn as well,
// returning the error from calling its Close method.
func (c *IdleTimingConn) Close() error {
	select {
	case c.closedCh <- true:
		// close accepted
	default:
		// already closing, ignore
	}
	return c.conn.Close()
}

func (c *IdleTimingConn) LocalAddr() net.Addr {
	return c.conn.LocalAddr()
}

func (c *IdleTimingConn) RemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}

func (c *IdleTimingConn) SetDeadline(t time.Time) error {
	c.SetReadDeadline(t)
	c.SetWriteDeadline(t)
	return nil
}

func (c *IdleTimingConn) SetReadDeadline(t time.Time) error {
	atomic.StoreInt64(&c.readDeadline, t.UnixNano())
	return nil
}

func (c *IdleTimingConn) SetWriteDeadline(t time.Time) error {
	atomic.StoreInt64(&c.writeDeadline, t.UnixNano())
	return nil
}

func (c *IdleTimingConn) markActive(n int) bool {
	if n > 0 {
		c.activeCh <- true
		return true
	} else {
		return false
	}
}

func isTimeout(err error) bool {
	if netErr, ok := err.(net.Error); ok {
		return netErr.Timeout()
	}
	return false
}
<|endoftext|>


<commit_before>// Package install provide features to download and install profiles
package install

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	progress "github.com/danielsoro/tomee-cli/util/progress"
	zip "github.com/danielsoro/tomee-cli/util/zip"
)

// Install an specif version of a profile in an path
func Install(tomeePath, dist string, version string) error {
	archiveURL, err := fetchArchiveURL(dist, version)
	if err != nil {
		return err
	}

	filename, err := downloadArchive(tomeePath, archiveURL)
	if err != nil {
		return err
	}

	unzipArchive(tomeePath, filename)
	err = filepath.Walk(strings.TrimSuffix(filename, ".zip"), grantPermition)
	if err != nil {
		return err
	}
	return nil
}

// Performs a GET request and return the content in []byte
func getFromURL(url string) ([]byte, error) {
	response, err := http.Get(url)
	var b []byte
	if err != nil {
		return nil, err
	}

	b, err = ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	response.Body.Close()
	return b, nil
}

// Find the correct URL mirror for the archive
func fetchArchiveURL(dist string, version string) (string, error) {
	var archiveURL string
	projectPathURL := fmt.Sprintf("/tomee/tomee-%s/apache-tomee-%s-%s.zip", version, version, dist)
	profileMirrorURL := fmt.Sprintf("http://www.apache.org/dyn/closer.cgi/%s", projectPathURL)
	htmlBody, err := getFromURL(profileMirrorURL)

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

	archiveURL = findArchiveURLFromHTMLBody(string(htmlBody), projectPathURL)
	return archiveURL, nil
}

// Parses the HTML body seeking the mirror link
func findArchiveURLFromHTMLBody(htmlBody string, projectPathURL string) string {
	// Retrieve the first occurence for the mirror link
	archiveURLRegex := "(https?://)?([0-9a-z.-]+)\\.([a-z.]{2,6})([\\/\\w.-]*)" + projectPathURL
	re := regexp.MustCompile(archiveURLRegex)
	archiveURL := re.FindString(htmlBody)
	return archiveURL
}

// Download from the specified URL to the path informed in the path flag
func downloadArchive(tomeePath string, archiveURL string) (string, error) {
	archiveURLSlice := strings.Split(archiveURL, "/")
	filepath := filepath.Join(tomeePath, archiveURLSlice[len(archiveURLSlice)-1])

	out, err := os.Create(filepath)
	defer out.Close()
	if err != nil {
		fmt.Println(fmt.Sprint(err))
		panic(err)
		return "", err
	}

	response, err := http.Get(archiveURL)
	defer response.Body.Close()
	if err != nil {
		fmt.Println(fmt.Sprint(err))
		panic(err)
		return "", err
	}

	_, er := io.Copy(out, &progress.ProgressLoader{Reader: response.Body, Length: response.ContentLength, Filepath: archiveURL})
	if er != nil {
		panic(err)
		return "", err
	}

	fmt.Printf("\nDownloaded %s with %d bytes\n", archiveURLSlice[len(archiveURLSlice)-1], response.ContentLength)

	return filepath, nil
}

// Unpack the archive to the specified folder
func unzipArchive(tomeePath string, filename string) {
	zip.Unzip(filename, tomeePath, true)
}

// Scans the bin directory, looking for executable files, and gives execution permission for them.
func grantPermition(path string, f os.FileInfo, err error) error {
	var extension string
	if runtime.GOOS == "windows" {
		extension = ".exe"
	} else {
		extension = ".sh"
	}

	if strings.HasSuffix(path, extension) {
		err := os.Chmod(path, 755)
		if err != nil {
			panic(err)
			return err
		}
	}
	return nil
}
<commit_msg>Update install.go<commit_after>// Package install provide features to download and install profiles
package install

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/danielsoro/tomee-cli/util/progress"
	"github.com/danielsoro/tomee-cli/util/zip"
)

// Install an specif version of a profile in an path
func Install(tomeePath, dist string, version string) error {
	archiveURL, err := fetchArchiveURL(dist, version)
	if err != nil {
		return err
	}

	filename, err := downloadArchive(tomeePath, archiveURL)
	if err != nil {
		return err
	}

	unzipArchive(tomeePath, filename)
	err = filepath.Walk(strings.TrimSuffix(filename, ".zip"), grantPermition)
	if err != nil {
		return err
	}
	return nil
}

// Performs a GET request and return the content in []byte
func getFromURL(url string) ([]byte, error) {
	response, err := http.Get(url)
	var b []byte
	if err != nil {
		return nil, err
	}

	b, err = ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	response.Body.Close()
	return b, nil
}

// Find the correct URL mirror for the archive
func fetchArchiveURL(dist string, version string) (string, error) {
	var archiveURL string
	projectPathURL := fmt.Sprintf("/tomee/tomee-%s/apache-tomee-%s-%s.zip", version, version, dist)
	profileMirrorURL := fmt.Sprintf("http://www.apache.org/dyn/closer.cgi/%s", projectPathURL)
	htmlBody, err := getFromURL(profileMirrorURL)

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

	archiveURL = findArchiveURLFromHTMLBody(string(htmlBody), projectPathURL)
	return archiveURL, nil
}

// Parses the HTML body seeking the mirror link
func findArchiveURLFromHTMLBody(htmlBody string, projectPathURL string) string {
	// Retrieve the first occurence for the mirror link
	archiveURLRegex := "(https?://)?([0-9a-z.-]+)\\.([a-z.]{2,6})([\\/\\w.-]*)" + projectPathURL
	re := regexp.MustCompile(archiveURLRegex)
	archiveURL := re.FindString(htmlBody)
	return archiveURL
}

// Download from the specified URL to the path informed in the path flag
func downloadArchive(tomeePath string, archiveURL string) (string, error) {
	archiveURLSlice := strings.Split(archiveURL, "/")
	filepath := filepath.Join(tomeePath, archiveURLSlice[len(archiveURLSlice)-1])

	out, err := os.Create(filepath)
	defer out.Close()
	if err != nil {
		fmt.Println(fmt.Sprint(err))
		panic(err)
		return "", err
	}

	response, err := http.Get(archiveURL)
	defer response.Body.Close()
	if err != nil {
		fmt.Println(fmt.Sprint(err))
		panic(err)
		return "", err
	}

	_, er := io.Copy(out, &progress.ProgressLoader{Reader: response.Body, Length: response.ContentLength, Filepath: archiveURL})
	if er != nil {
		panic(err)
		return "", err
	}

	fmt.Printf("\nDownloaded %s with %d bytes\n", archiveURLSlice[len(archiveURLSlice)-1], response.ContentLength)

	return filepath, nil
}

// Unpack the archive to the specified folder
func unzipArchive(tomeePath string, filename string) {
	zip.Unzip(filename, tomeePath, true)
}

// Scans the bin directory, looking for executable files, and gives execution permission for them.
func grantPermition(path string, f os.FileInfo, err error) error {
	var extension string
	if runtime.GOOS == "windows" {
		extension = ".exe"
	} else {
		extension = ".sh"
	}

	if strings.HasSuffix(path, extension) {
		err := os.Chmod(path, 755)
		if err != nil {
			panic(err)
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package apns

import (
	"bytes"
	"encoding/binary"
	"log"
	"fmt"
	"encoding/hex"
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

// Push commands always start with command value 2.
const pushCommandValue = 2

// Your total notification payload cannot exceed 2 KB.
const MaxPayloadSizeBytes = 2048

// Every push notification gets a pseudo-unique identifier;
// this establishes the upper boundary for it. Apple will return
// this identifier if there is an issue sending your notification.
const IdentifierUbound = 9999

// Constants related to the payload fields and their lengths.
const (
	deviceTokenItemid            = 1
	payloadItemid                = 2
	notificationIdentifierItemid = 3
	expirationDateItemid         = 4
	priorityItemid               = 5
	deviceTokenLength            = 32
	notificationIdentifierLength = 4
	expirationDateLength         = 4
	priorityLength               = 1
)

// Payload contains the notification data for your request.
//
// Alert is an interface here because it supports either a string
// or a dictionary, represented within by an AlertDictionary struct.
type Payload struct {
	Alert            interface{} `json:"alert,omitempty"`
	Badge            int         `json:"badge,omitempty"`
	Sound            string      `json:"sound,omitempty"`
	ContentAvailable int         `json:"content-available,omitempty"`
	Category         string      `json:"category,omitempty"`
}

// NewPayload creates and returns a Payload structure.
func NewPayload() *Payload {
	return new(Payload)
}

// AlertDictionary is a more complex notification payload.
//
// From the APN docs: "Use the ... alert dictionary in general only if you absolutely need to."
// The AlertDictionary is suitable for specific localization needs.
type AlertDictionary struct {
	Body         string   `json:"body,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

// NewAlertDictionary creates and returns an AlertDictionary structure.
func NewAlertDictionary() *AlertDictionary {
	return new(AlertDictionary)
}

// PushNotification is the wrapper for the Payload.
// The length fields are computed in ToBytes() and aren't represented here.
type PushNotification struct {
	Identifier  int32 `json:"identifier"`
	Expiry      uint32 `json:"expiry"`
	DeviceToken string `json:"device_token"`
	Payload     map[string]interface{} `json:"payload"`
	Priority    uint8 `json:"priority"`
}

// NewPushNotification creates and returns a PushNotification structure.
// It also initializes the pseudo-random identifier.
func NewPushNotification() (pn *PushNotification) {
	pn = new(PushNotification)
	pn.Payload = make(map[string]interface{})
	pn.Identifier = rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(IdentifierUbound)
	pn.Priority = 10
	return
}

// AddPayload sets the "aps" Payload section of the request. It also
// has a hack described within to deal with specific zero values.
func (pn *PushNotification) AddPayload(p *Payload) {
	// This deserves some explanation.
	//
	// Setting an exported field of type int to 0
	// triggers the omitempty behavior if you've set it.
	// Since the badge is optional, we should omit it if
	// it's not set. However, we want to include it if the
	// value is 0, so there's a hack in push_notification.go
	// that exploits the fact that Apple treats -1 for a
	// badge value as though it were 0 (i.e. it clears the
	// badge but doesn't stop the notification from going
	// through successfully.)
	//
	// Still a hack though :)
	if p.Badge == 0 {
		p.Badge = -1
	}
	pn.Set("aps", p)
}

// Get returns the value of a Payload key, if it exists.
func (pn *PushNotification) Get(key string) interface{} {
	return pn.Payload[key]
}

// Set defines the value of a Payload key.
func (pn *PushNotification) Set(key string, value interface{}) {
	pn.Payload[key] = value
}

// PayloadJSON returns the current Payload in JSON format.
func (pn *PushNotification) PayloadJSON() ([]byte, error) {
	return json.Marshal(pn.Payload)
}

// PayloadString returns the current Payload in string format.
func (pn *PushNotification) PayloadString() (string, error) {
	j, err := pn.PayloadJSON()
	return string(j), err
}

// ToBytes returns a byte array of the complete PushNotification
// struct. This array is what should be transmitted to the APN Service.
func (pn *PushNotification) ToBytes() ([]byte, error) {
	log.Printf("DV TK IS: %s\n", pn.DeviceToken)
	tokenHex, err := hex.DecodeString(pn.DeviceToken)
	log.Printf("token hex: %s\n", string(tokenHex))
	if err != nil {
		return nil, err
	}
	if len(tokenHex) != deviceTokenLength {
		return nil, errors.New(fmt.Sprintf("device token has incorrect length %d was supposed to be %d. TOKEN IS %+v BUT ORIGINAL IS %+v", len(tokenHex), deviceTokenLength, tokenHex, pn.DeviceToken))
	}
	payload, err := pn.PayloadJSON()
	if err != nil {
		return nil, err
	}
	if len(payload) > MaxPayloadSizeBytes {
		return nil, errors.New("payload is larger than the " + strconv.Itoa(MaxPayloadSizeBytes) + " byte limit")
	}

	frameBuffer := new(bytes.Buffer)
	binary.Write(frameBuffer, binary.BigEndian, uint8(deviceTokenItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(deviceTokenLength))
	binary.Write(frameBuffer, binary.BigEndian, tokenHex)
	binary.Write(frameBuffer, binary.BigEndian, uint8(payloadItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(len(payload)))
	binary.Write(frameBuffer, binary.BigEndian, payload)
	binary.Write(frameBuffer, binary.BigEndian, uint8(notificationIdentifierItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(notificationIdentifierLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Identifier)
	binary.Write(frameBuffer, binary.BigEndian, uint8(expirationDateItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(expirationDateLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Expiry)
	binary.Write(frameBuffer, binary.BigEndian, uint8(priorityItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(priorityLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Priority)

	buffer := bytes.NewBuffer([]byte{})
	binary.Write(buffer, binary.BigEndian, uint8(pushCommandValue))
	binary.Write(buffer, binary.BigEndian, uint32(frameBuffer.Len()))
	binary.Write(buffer, binary.BigEndian, frameBuffer.Bytes())
	return buffer.Bytes(), nil
}
<commit_msg>Fixing bad json format on DeviceToken<commit_after>package apns

import (
	"bytes"
	"encoding/binary"
	"log"
	"fmt"
	"encoding/hex"
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

// Push commands always start with command value 2.
const pushCommandValue = 2

// Your total notification payload cannot exceed 2 KB.
const MaxPayloadSizeBytes = 2048

// Every push notification gets a pseudo-unique identifier;
// this establishes the upper boundary for it. Apple will return
// this identifier if there is an issue sending your notification.
const IdentifierUbound = 9999

// Constants related to the payload fields and their lengths.
const (
	deviceTokenItemid            = 1
	payloadItemid                = 2
	notificationIdentifierItemid = 3
	expirationDateItemid         = 4
	priorityItemid               = 5
	deviceTokenLength            = 32
	notificationIdentifierLength = 4
	expirationDateLength         = 4
	priorityLength               = 1
)

// Payload contains the notification data for your request.
//
// Alert is an interface here because it supports either a string
// or a dictionary, represented within by an AlertDictionary struct.
type Payload struct {
	Alert            interface{} `json:"alert,omitempty"`
	Badge            int         `json:"badge,omitempty"`
	Sound            string      `json:"sound,omitempty"`
	ContentAvailable int         `json:"content-available,omitempty"`
	Category         string      `json:"category,omitempty"`
}

// NewPayload creates and returns a Payload structure.
func NewPayload() *Payload {
	return new(Payload)
}

// AlertDictionary is a more complex notification payload.
//
// From the APN docs: "Use the ... alert dictionary in general only if you absolutely need to."
// The AlertDictionary is suitable for specific localization needs.
type AlertDictionary struct {
	Body         string   `json:"body,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

// NewAlertDictionary creates and returns an AlertDictionary structure.
func NewAlertDictionary() *AlertDictionary {
	return new(AlertDictionary)
}

// PushNotification is the wrapper for the Payload.
// The length fields are computed in ToBytes() and aren't represented here.
type PushNotification struct {
	Identifier  int32 `json:"identifier"`
	Expiry      uint32 `json:"expiry"`
	DeviceToken string `json:"devicetoken"`
	Payload     map[string]interface{} `json:"payload"`
	Priority    uint8 `json:"priority"`
}

// NewPushNotification creates and returns a PushNotification structure.
// It also initializes the pseudo-random identifier.
func NewPushNotification() (pn *PushNotification) {
	pn = new(PushNotification)
	pn.Payload = make(map[string]interface{})
	pn.Identifier = rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(IdentifierUbound)
	pn.Priority = 10
	return
}

// AddPayload sets the "aps" Payload section of the request. It also
// has a hack described within to deal with specific zero values.
func (pn *PushNotification) AddPayload(p *Payload) {
	// This deserves some explanation.
	//
	// Setting an exported field of type int to 0
	// triggers the omitempty behavior if you've set it.
	// Since the badge is optional, we should omit it if
	// it's not set. However, we want to include it if the
	// value is 0, so there's a hack in push_notification.go
	// that exploits the fact that Apple treats -1 for a
	// badge value as though it were 0 (i.e. it clears the
	// badge but doesn't stop the notification from going
	// through successfully.)
	//
	// Still a hack though :)
	if p.Badge == 0 {
		p.Badge = -1
	}
	pn.Set("aps", p)
}

// Get returns the value of a Payload key, if it exists.
func (pn *PushNotification) Get(key string) interface{} {
	return pn.Payload[key]
}

// Set defines the value of a Payload key.
func (pn *PushNotification) Set(key string, value interface{}) {
	pn.Payload[key] = value
}

// PayloadJSON returns the current Payload in JSON format.
func (pn *PushNotification) PayloadJSON() ([]byte, error) {
	return json.Marshal(pn.Payload)
}

// PayloadString returns the current Payload in string format.
func (pn *PushNotification) PayloadString() (string, error) {
	j, err := pn.PayloadJSON()
	return string(j), err
}

// ToBytes returns a byte array of the complete PushNotification
// struct. This array is what should be transmitted to the APN Service.
func (pn *PushNotification) ToBytes() ([]byte, error) {
	log.Printf("DV TK IS: %s\n", pn.DeviceToken)
	tokenHex, err := hex.DecodeString(pn.DeviceToken)
	log.Printf("token hex: %s\n", string(tokenHex))
	if err != nil {
		return nil, err
	}
	if len(tokenHex) != deviceTokenLength {
		return nil, errors.New(fmt.Sprintf("device token has incorrect length %d was supposed to be %d. TOKEN IS %+v BUT ORIGINAL IS %+v", len(tokenHex), deviceTokenLength, tokenHex, pn.DeviceToken))
	}
	payload, err := pn.PayloadJSON()
	if err != nil {
		return nil, err
	}
	if len(payload) > MaxPayloadSizeBytes {
		return nil, errors.New("payload is larger than the " + strconv.Itoa(MaxPayloadSizeBytes) + " byte limit")
	}

	frameBuffer := new(bytes.Buffer)
	binary.Write(frameBuffer, binary.BigEndian, uint8(deviceTokenItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(deviceTokenLength))
	binary.Write(frameBuffer, binary.BigEndian, tokenHex)
	binary.Write(frameBuffer, binary.BigEndian, uint8(payloadItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(len(payload)))
	binary.Write(frameBuffer, binary.BigEndian, payload)
	binary.Write(frameBuffer, binary.BigEndian, uint8(notificationIdentifierItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(notificationIdentifierLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Identifier)
	binary.Write(frameBuffer, binary.BigEndian, uint8(expirationDateItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(expirationDateLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Expiry)
	binary.Write(frameBuffer, binary.BigEndian, uint8(priorityItemid))
	binary.Write(frameBuffer, binary.BigEndian, uint16(priorityLength))
	binary.Write(frameBuffer, binary.BigEndian, pn.Priority)

	buffer := bytes.NewBuffer([]byte{})
	binary.Write(buffer, binary.BigEndian, uint8(pushCommandValue))
	binary.Write(buffer, binary.BigEndian, uint32(frameBuffer.Len()))
	binary.Write(buffer, binary.BigEndian, frameBuffer.Bytes())
	return buffer.Bytes(), nil
}
<|endoftext|>