<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validation

import (
	"reflect"
	"sort"

	"k8s.io/kube-openapi/pkg/util/proto"
)

type validationItem interface {
	proto.SchemaVisitor

	Errors() []error
	Path() *proto.Path
}

type baseItem struct {
	errors errors
	path   proto.Path
}

// Errors returns the list of errors found for this item.
func (item *baseItem) Errors() []error {
	return item.errors.Errors()
}

// AddValidationError wraps the given error into a ValidationError and
// attaches it to this item.
func (item *baseItem) AddValidationError(err error) {
	item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err})
}

// AddError adds a regular (non-validation related) error to the list.
func (item *baseItem) AddError(err error) {
	item.errors.AppendErrors(err)
}

// CopyErrors adds a list of errors to this item. This is useful to copy
// errors from subitems.
func (item *baseItem) CopyErrors(errs []error) {
	item.errors.AppendErrors(errs...)
}

// Path returns the path of this item, helps print useful errors.
func (item *baseItem) Path() *proto.Path {
	return &item.path
}

// mapItem represents a map entry in the yaml.
type mapItem struct {
	baseItem

	Map map[string]interface{}
}

func (item *mapItem) sortedKeys() []string {
	sortedKeys := []string{}
	for key := range item.Map {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)
	return sortedKeys
}

var _ validationItem = &mapItem{}

func (item *mapItem) VisitPrimitive(schema *proto.Primitive) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"})
}

func (item *mapItem) VisitArray(schema *proto.Array) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
}

func (item *mapItem) VisitMap(schema *proto.Map) {
	for _, key := range item.sortedKeys() {
		subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
		if err != nil {
			item.AddError(err)
			continue
		}
		schema.SubType.Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}
}

func (item *mapItem) VisitKind(schema *proto.Kind) {
	// Verify each sub-field.
	for _, key := range item.sortedKeys() {
		if item.Map[key] == nil {
			continue
		}
		subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
		if err != nil {
			item.AddError(err)
			continue
		}
		if _, ok := schema.Fields[key]; !ok {
			item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key})
			continue
		}
		schema.Fields[key].Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}

	// Verify that all required fields are present.
	for _, required := range schema.RequiredFields {
		if v, ok := item.Map[required]; !ok || v == nil {
			item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required})
		}
	}
}

func (item *mapItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// arrayItem represents a yaml array.
type arrayItem struct {
	baseItem

	Array []interface{}
}

var _ validationItem = &arrayItem{}

func (item *arrayItem) VisitPrimitive(schema *proto.Primitive) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"})
}

func (item *arrayItem) VisitArray(schema *proto.Array) {
	for i, v := range item.Array {
		path := item.Path().ArrayPath(i)
		if v == nil {
			item.AddValidationError(InvalidObjectTypeError{Type: "nil", Path: path.String()})
			continue
		}
		subItem, err := itemFactory(path, v)
		if err != nil {
			item.AddError(err)
			continue
		}
		schema.SubType.Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}
}

func (item *arrayItem) VisitMap(schema *proto.Map) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
}

func (item *arrayItem) VisitKind(schema *proto.Kind) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
}

func (item *arrayItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// primitiveItem represents a yaml value.
type primitiveItem struct {
	baseItem

	Value interface{}
	Kind  string
}

var _ validationItem = &primitiveItem{}

func (item *primitiveItem) VisitPrimitive(schema *proto.Primitive) {
	// Some types of primitives can match more than one (a number
	// can be a string, but not the other way around). Return from
	// the switch if we have a valid possible type conversion
	// NOTE(apelisse): This logic is blindly copied from the
	// existing swagger logic, and I'm not sure I agree with it.
	switch schema.Type {
	case proto.Boolean:
		switch item.Kind {
		case proto.Boolean:
			return
		}
	case proto.Integer:
		switch item.Kind {
		case proto.Integer, proto.Number:
			return
		}
	case proto.Number:
		switch item.Kind {
		case proto.Number:
			return
		}
	case proto.String:
		return
	}

	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind})
}

func (item *primitiveItem) VisitArray(schema *proto.Array) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind})
}

func (item *primitiveItem) VisitMap(schema *proto.Map) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}

func (item *primitiveItem) VisitKind(schema *proto.Kind) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}

func (item *primitiveItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// itemFactory creates the relevant item type/visitor based on the current yaml type.
func itemFactory(path proto.Path, v interface{}) (validationItem, error) {
	// We need to special case for no-type fields in yaml (e.g. empty item in list)
	if v == nil {
		return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()}
	}
	kind := reflect.TypeOf(v).Kind()
	switch kind {
	case reflect.Bool:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Boolean,
		}, nil
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64,
		reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Integer,
		}, nil
	case reflect.Float32,
		reflect.Float64:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Number,
		}, nil
	case reflect.String:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.String,
		}, nil
	case reflect.Array,
		reflect.Slice:
		return &arrayItem{
			baseItem: baseItem{path: path},
			Array:    v.([]interface{}),
		}, nil
	case reflect.Map:
		return &mapItem{
			baseItem: baseItem{path: path},
			Map:      v.(map[string]interface{}),
		}, nil
	}
	return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()}
}
<commit_msg>Fix mapping and kind validation errors when array is passed<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validation

import (
	"reflect"
	"sort"

	"k8s.io/kube-openapi/pkg/util/proto"
)

type validationItem interface {
	proto.SchemaVisitor

	Errors() []error
	Path() *proto.Path
}

type baseItem struct {
	errors errors
	path   proto.Path
}

// Errors returns the list of errors found for this item.
func (item *baseItem) Errors() []error {
	return item.errors.Errors()
}

// AddValidationError wraps the given error into a ValidationError and
// attaches it to this item.
func (item *baseItem) AddValidationError(err error) {
	item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err})
}

// AddError adds a regular (non-validation related) error to the list.
func (item *baseItem) AddError(err error) {
	item.errors.AppendErrors(err)
}

// CopyErrors adds a list of errors to this item. This is useful to copy
// errors from subitems.
func (item *baseItem) CopyErrors(errs []error) {
	item.errors.AppendErrors(errs...)
}

// Path returns the path of this item, helps print useful errors.
func (item *baseItem) Path() *proto.Path {
	return &item.path
}

// mapItem represents a map entry in the yaml.
type mapItem struct {
	baseItem

	Map map[string]interface{}
}

func (item *mapItem) sortedKeys() []string {
	sortedKeys := []string{}
	for key := range item.Map {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)
	return sortedKeys
}

var _ validationItem = &mapItem{}

func (item *mapItem) VisitPrimitive(schema *proto.Primitive) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"})
}

func (item *mapItem) VisitArray(schema *proto.Array) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
}

func (item *mapItem) VisitMap(schema *proto.Map) {
	for _, key := range item.sortedKeys() {
		subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
		if err != nil {
			item.AddError(err)
			continue
		}
		schema.SubType.Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}
}

func (item *mapItem) VisitKind(schema *proto.Kind) {
	// Verify each sub-field.
	for _, key := range item.sortedKeys() {
		if item.Map[key] == nil {
			continue
		}
		subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
		if err != nil {
			item.AddError(err)
			continue
		}
		if _, ok := schema.Fields[key]; !ok {
			item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key})
			continue
		}
		schema.Fields[key].Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}

	// Verify that all required fields are present.
	for _, required := range schema.RequiredFields {
		if v, ok := item.Map[required]; !ok || v == nil {
			item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required})
		}
	}
}

func (item *mapItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// arrayItem represents a yaml array.
type arrayItem struct {
	baseItem

	Array []interface{}
}

var _ validationItem = &arrayItem{}

func (item *arrayItem) VisitPrimitive(schema *proto.Primitive) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"})
}

func (item *arrayItem) VisitArray(schema *proto.Array) {
	for i, v := range item.Array {
		path := item.Path().ArrayPath(i)
		if v == nil {
			item.AddValidationError(InvalidObjectTypeError{Type: "nil", Path: path.String()})
			continue
		}
		subItem, err := itemFactory(path, v)
		if err != nil {
			item.AddError(err)
			continue
		}
		schema.SubType.Accept(subItem)
		item.CopyErrors(subItem.Errors())
	}
}

func (item *arrayItem) VisitMap(schema *proto.Map) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"})
}

func (item *arrayItem) VisitKind(schema *proto.Kind) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"})
}

func (item *arrayItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// primitiveItem represents a yaml value.
type primitiveItem struct {
	baseItem

	Value interface{}
	Kind  string
}

var _ validationItem = &primitiveItem{}

func (item *primitiveItem) VisitPrimitive(schema *proto.Primitive) {
	// Some types of primitives can match more than one (a number
	// can be a string, but not the other way around). Return from
	// the switch if we have a valid possible type conversion
	// NOTE(apelisse): This logic is blindly copied from the
	// existing swagger logic, and I'm not sure I agree with it.
	switch schema.Type {
	case proto.Boolean:
		switch item.Kind {
		case proto.Boolean:
			return
		}
	case proto.Integer:
		switch item.Kind {
		case proto.Integer, proto.Number:
			return
		}
	case proto.Number:
		switch item.Kind {
		case proto.Number:
			return
		}
	case proto.String:
		return
	}

	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind})
}

func (item *primitiveItem) VisitArray(schema *proto.Array) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind})
}

func (item *primitiveItem) VisitMap(schema *proto.Map) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}

func (item *primitiveItem) VisitKind(schema *proto.Kind) {
	item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}

func (item *primitiveItem) VisitReference(schema proto.Reference) {
	// passthrough
	schema.SubSchema().Accept(item)
}

// itemFactory creates the relevant item type/visitor based on the current yaml type.
func itemFactory(path proto.Path, v interface{}) (validationItem, error) {
	// We need to special case for no-type fields in yaml (e.g. empty item in list)
	if v == nil {
		return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()}
	}
	kind := reflect.TypeOf(v).Kind()
	switch kind {
	case reflect.Bool:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Boolean,
		}, nil
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64,
		reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Integer,
		}, nil
	case reflect.Float32,
		reflect.Float64:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.Number,
		}, nil
	case reflect.String:
		return &primitiveItem{
			baseItem: baseItem{path: path},
			Value:    v,
			Kind:     proto.String,
		}, nil
	case reflect.Array,
		reflect.Slice:
		return &arrayItem{
			baseItem: baseItem{path: path},
			Array:    v.([]interface{}),
		}, nil
	case reflect.Map:
		return &mapItem{
			baseItem: baseItem{path: path},
			Map:      v.(map[string]interface{}),
		}, nil
	}
	return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()}
}
<|endoftext|>


<commit_before>package lzma

const (
	lenStates     = 4
	startPosModel = 4
	endPosModel   = 14
	posSlotBits   = 6
	alignBits     = 4
	maxPosSlot    = 63
)

type distCodec struct {
	posSlotCodecs [lenStates]treeCodec
	posModel      [endPosModel - startPosModel]treeReverseCodec
	alignCodec    treeReverseCodec
}

func newDistCodec() *distCodec {
	dc := new(distCodec)
	for i := range dc.posSlotCodecs {
		dc.posSlotCodecs[i] = makeTreeCodec(posSlotBits)
	}
	for i := range dc.posModel {
		posSlot := startPosModel + i
		bits := (posSlot >> 1) - 1
		dc.posModel[i] = makeTreeReverseCodec(bits)
	}
	dc.alignCodec = makeTreeReverseCodec(alignBits)
	return dc
}

func lenState(l uint32) uint32 {
	s := l
	if s >= lenStates {
		s = lenStates - 1
	}
	return s
}

func (dc *distCodec) Encode(dist uint32, l uint32, e *rangeEncoder,
) (err error) {
	// Compute the posSlot using nlz32
	var posSlot uint32
	var bits uint32
	if dist < startPosModel {
		posSlot = dist
	} else {
		bits = uint32(30 - nlz32(dist))
		posSlot = startPosModel - 2 + (bits << 1)
		posSlot += (dist >> uint(bits)) & 1
	}

	if err = dc.posSlotCodecs[lenState(l)].Encode(posSlot, e); err != nil {
		return
	}

	switch {
	case posSlot < startPosModel:
		return nil
	case posSlot < endPosModel:
		tc := &dc.posModel[posSlot-startPosModel]
		return tc.Encode(dist, e)
	}
	dic := directCodec(bits - alignBits)
	if err = dic.Encode(dist>>alignBits, e); err != nil {
		return
	}
	return dc.alignCodec.Encode(dist, e)
}

func (dc *distCodec) Decode(l uint32, d *rangeDecoder,
) (dist uint32, err error) {
	posSlot, err := dc.posSlotCodecs[lenState(l)].Decode(d)
	if err != nil {
		return
	}
	if posSlot < startPosModel {
		return posSlot, nil
	}

	bits := (posSlot >> 1) - 1
	dist = (2 | (posSlot & 1)) << bits
	var u uint32
	if posSlot < endPosModel {
		tc := &dc.posModel[posSlot-startPosModel]
		if u, err = tc.Decode(d); err != nil {
			return 0, err
		}
		dist += u
		return dist, nil
	}

	dic := directCodec(bits - alignBits)
	if u, err = dic.Decode(d); err != nil {
		return 0, err
	}
	dist += u << alignBits
	if u, err = dc.alignCodec.Decode(d); err != nil {
		return 0, err
	}
	dist += u
	return dist, nil
}
<commit_msg>lzma: added documentation comments for dist_codec.go<commit_after>package lzma

// Constants used by the distance codec.
const (
	// number of the supported len states
	lenStates = 4
	// start for the position models
	startPosModel = 4
	// first index with align bits support
	endPosModel = 14
	// bits for the position slots
	posSlotBits = 6
	// number of align bits
	alignBits = 4
	// maximum positon slot
	maxPosSlot = 63
)

// distCodec provides encoding and decoding of distance values. It support
// values for dist from 0 to 2^32-1. Note the real match distance is one
// higher.
type distCodec struct {
	posSlotCodecs [lenStates]treeCodec
	posModel      [endPosModel - startPosModel]treeReverseCodec
	alignCodec    treeReverseCodec
}

// newDistCodec creates a new distance codec.
func newDistCodec() *distCodec {
	dc := new(distCodec)
	for i := range dc.posSlotCodecs {
		dc.posSlotCodecs[i] = makeTreeCodec(posSlotBits)
	}
	for i := range dc.posModel {
		posSlot := startPosModel + i
		bits := (posSlot >> 1) - 1
		dc.posModel[i] = makeTreeReverseCodec(bits)
	}
	dc.alignCodec = makeTreeReverseCodec(alignBits)
	return dc
}

// Converts the value l to a supported lenState value.
func lenState(l uint32) uint32 {
	s := l
	if s >= lenStates {
		s = lenStates - 1
	}
	return s
}

// Encode encodes the distance using the parameter l. Dist can have values from
// the full range of uint32 values.
func (dc *distCodec) Encode(dist uint32, l uint32, e *rangeEncoder,
) (err error) {
	// Compute the posSlot using nlz32
	var posSlot uint32
	var bits uint32
	if dist < startPosModel {
		posSlot = dist
	} else {
		bits = uint32(30 - nlz32(dist))
		posSlot = startPosModel - 2 + (bits << 1)
		posSlot += (dist >> uint(bits)) & 1
	}

	if err = dc.posSlotCodecs[lenState(l)].Encode(posSlot, e); err != nil {
		return
	}

	switch {
	case posSlot < startPosModel:
		return nil
	case posSlot < endPosModel:
		tc := &dc.posModel[posSlot-startPosModel]
		return tc.Encode(dist, e)
	}
	dic := directCodec(bits - alignBits)
	if err = dic.Encode(dist>>alignBits, e); err != nil {
		return
	}
	return dc.alignCodec.Encode(dist, e)
}

// Decode decodes the distance using the parameter l.
func (dc *distCodec) Decode(l uint32, d *rangeDecoder,
) (dist uint32, err error) {
	posSlot, err := dc.posSlotCodecs[lenState(l)].Decode(d)
	if err != nil {
		return
	}

	// posSlot equals distance
	if posSlot < startPosModel {
		return posSlot, nil
	}

	// posSlot are using the individial models
	bits := (posSlot >> 1) - 1
	dist = (2 | (posSlot & 1)) << bits
	var u uint32
	if posSlot < endPosModel {
		tc := &dc.posModel[posSlot-startPosModel]
		if u, err = tc.Decode(d); err != nil {
			return 0, err
		}
		dist += u
		return dist, nil
	}

	// posSlots use direct encoding and a single model for the four align
	// bits.
	dic := directCodec(bits - alignBits)
	if u, err = dic.Decode(d); err != nil {
		return 0, err
	}
	dist += u << alignBits
	if u, err = dc.alignCodec.Decode(d); err != nil {
		return 0, err
	}
	dist += u
	return dist, nil
}
<|endoftext|>


<commit_before>package chat

import (
	"fmt"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/teams"
	context "golang.org/x/net/context"
)

type TeamsNameInfoSource struct {
	globals.Contextified
	utils.DebugLabeler
}

func NewTeamsNameInfoSource(g *globals.Context) *TeamsNameInfoSource {
	return &TeamsNameInfoSource{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "TeamsNameInfoSource", false),
	}
}

func (t *TeamsNameInfoSource) Lookup(ctx context.Context, name string, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	defer t.Trace(ctx, func() error { return err }, fmt.Sprintf("Lookup(%s)", name))()

	team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{
		Name:        name, // Loading by name is a last resort and will always cause an extra roundtrip.
		ForceRepoll: true,
	})
	if err != nil {
		return res, err
	}
	return teamToNameInfo(ctx, team, vis)
}

func teamToNameInfo(ctx context.Context, team *teams.Team, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	res.ID, err = teamIDToTLFID(team.ID)
	if err != nil {
		return res, err
	}
	res.CanonicalName = team.Name().String()

	if vis == chat1.TLFVisibility_PRIVATE {
		chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
		if err != nil {
			return res, err
		}
		for _, key := range chatKeys {
			res.CryptKeys = append(res.CryptKeys, key)
		}
	}
	return res, nil
}

type ImplicitTeamsNameInfoSource struct {
	globals.Contextified
	utils.DebugLabeler
}

func NewImplicitTeamsNameInfoSource(g *globals.Context) *ImplicitTeamsNameInfoSource {
	return &ImplicitTeamsNameInfoSource{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "ImplicitTeamsNameInfoSource", false),
	}
}

func (t *ImplicitTeamsNameInfoSource) Lookup(ctx context.Context, name string, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	// XXX check if name is prefixed
	if strings.HasPrefix(name, keybase1.ImplicitTeamPrefix) {
		team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{Name: name})
		if err != nil {
			return res, err
		}
		res.ID, err = teamIDToTLFID(team.ID)
		if err != nil {
			return res, err
		}
		res.CanonicalName, err = team.ImplicitTeamDisplayName(ctx)
		if vis == chat1.TLFVisibility_PRIVATE {
			chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
			if err != nil {
				return res, err
			}
			for _, key := range chatKeys {
				res.CryptKeys = append(res.CryptKeys, key)
			}
		}
		return res, nil
	}

	teamID, teamName, err := teams.LookupImplicitTeam(ctx, t.G().ExternalG(), name, vis == chat1.TLFVisibility_PUBLIC)
	if err != nil {
		return res, err
	}

	if !teamID.IsRootTeam() {
		panic(fmt.Sprintf("implicit team found via LookupImplicitTeam not root team: %s", teamID))
	}

	res.ID, err = teamIDToTLFID(teamID)
	if err != nil {
		return res, err
	}

	res.CanonicalName = teamName.String()

	if vis == chat1.TLFVisibility_PRIVATE {
		team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{ID: teamID})
		if err != nil {
			return res, err
		}
		chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
		if err != nil {
			return res, err
		}
		for _, key := range chatKeys {
			res.CryptKeys = append(res.CryptKeys, key)
		}
	}

	return res, nil
}
<commit_msg>Refactor<commit_after>package chat

import (
	"fmt"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/teams"
	context "golang.org/x/net/context"
)

type TeamsNameInfoSource struct {
	globals.Contextified
	utils.DebugLabeler
}

func NewTeamsNameInfoSource(g *globals.Context) *TeamsNameInfoSource {
	return &TeamsNameInfoSource{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "TeamsNameInfoSource", false),
	}
}

func (t *TeamsNameInfoSource) Lookup(ctx context.Context, name string, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	defer t.Trace(ctx, func() error { return err }, fmt.Sprintf("Lookup(%s)", name))()

	team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{
		Name:        name, // Loading by name is a last resort and will always cause an extra roundtrip.
		ForceRepoll: true,
	})
	if err != nil {
		return res, err
	}
	return teamToNameInfo(ctx, team, vis)
}

func teamToNameInfo(ctx context.Context, team *teams.Team, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	res.ID, err = teamIDToTLFID(team.ID)
	if err != nil {
		return res, err
	}
	res.CanonicalName = team.Name().String()

	if vis == chat1.TLFVisibility_PRIVATE {
		chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
		if err != nil {
			return res, err
		}
		for _, key := range chatKeys {
			res.CryptKeys = append(res.CryptKeys, key)
		}
	}
	return res, nil
}

type ImplicitTeamsNameInfoSource struct {
	globals.Contextified
	utils.DebugLabeler
}

func NewImplicitTeamsNameInfoSource(g *globals.Context) *ImplicitTeamsNameInfoSource {
	return &ImplicitTeamsNameInfoSource{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "ImplicitTeamsNameInfoSource", false),
	}
}

func (t *ImplicitTeamsNameInfoSource) Lookup(ctx context.Context, name string, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	// check if name is prefixed
	if strings.HasPrefix(name, keybase1.ImplicitTeamPrefix) {
		return t.lookupInternalName(ctx, name, vis)
	}

	teamID, teamName, err := teams.LookupImplicitTeam(ctx, t.G().ExternalG(), name, vis == chat1.TLFVisibility_PUBLIC)
	if err != nil {
		return res, err
	}

	if !teamID.IsRootTeam() {
		panic(fmt.Sprintf("implicit team found via LookupImplicitTeam not root team: %s", teamID))
	}

	res.ID, err = teamIDToTLFID(teamID)
	if err != nil {
		return res, err
	}

	res.CanonicalName = teamName.String()

	if vis == chat1.TLFVisibility_PRIVATE {
		team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{ID: teamID})
		if err != nil {
			return res, err
		}
		chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
		if err != nil {
			return res, err
		}
		for _, key := range chatKeys {
			res.CryptKeys = append(res.CryptKeys, key)
		}
	}

	return res, nil
}

func (t *ImplicitTeamsNameInfoSource) lookupInternalName(ctx context.Context, name string, vis chat1.TLFVisibility) (res types.NameInfo, err error) {
	team, err := teams.Load(ctx, t.G().ExternalG(), keybase1.LoadTeamArg{Name: name})
	if err != nil {
		return res, err
	}
	res.ID, err = teamIDToTLFID(team.ID)
	if err != nil {
		return res, err
	}
	res.CanonicalName, err = team.ImplicitTeamDisplayName(ctx)
	if vis == chat1.TLFVisibility_PRIVATE {
		chatKeys, err := team.AllApplicationKeys(ctx, keybase1.TeamApplication_CHAT)
		if err != nil {
			return res, err
		}
		for _, key := range chatKeys {
			res.CryptKeys = append(res.CryptKeys, key)
		}
	}
	return res, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"

	jsonw "github.com/keybase/go-jsonw"
)

type JSONFile struct {
	filename string
	which    string
	jw       *jsonw.Wrapper
	exists   bool
	dirty    bool
	Contextified
}

func NewJSONFile(g *GlobalContext, filename, which string) *JSONFile {
	return &JSONFile{
		filename:     filename,
		which:        which,
		jw:           jsonw.NewDictionary(),
		Contextified: NewContextified(g),
	}
}

func (f *JSONFile) GetWrapper() *jsonw.Wrapper {
	return f.jw
}
func (f *JSONFile) Exists() bool { return f.exists }

func (f *JSONFile) Load(warnOnNotFound bool) error {
	f.G().Log.Debug("+ loading %s file: %s", f.which, f.filename)
	file, err := os.Open(f.filename)
	if err != nil {
		if os.IsNotExist(err) {
			msg := fmt.Sprintf("No %s file found; tried %s", f.which, f.filename)
			if warnOnNotFound {
				f.G().Log.Warning(msg)
			} else {
				f.G().Log.Debug(msg)
			}
			return nil
		} else if os.IsPermission(err) {
			f.G().Log.Warning("Permission denied opening %s file %s", f.which, f.filename)
			return nil
		} else {
			return err
		}
	}
	f.exists = true
	defer file.Close()
	decoder := json.NewDecoder(file)
	obj := make(map[string]interface{})
	// Treat empty files like an empty dictionary
	if err = decoder.Decode(&obj); err != nil && err != io.EOF {
		f.G().Log.Errorf("Error decoding %s file %s", f.which, f.filename)
		return err
	}
	f.jw = jsonw.NewWrapper(obj)
	f.G().Log.Debug("- successfully loaded %s file", f.which)
	return nil
}

func (f *JSONFile) MaybeSave(pretty bool, mode os.FileMode) (err error) {
	if f != nil && f.dirty {
		err = f.Save(pretty, mode)
	}
	return
}

func (f *JSONFile) Nuke() error {
	f.G().Log.Debug("+ nuke file %s", f.filename)

	err := os.Remove(f.filename)
	f.G().Log.Debug("- nuke file %s -> %s", f.filename, ErrToOk(err))

	return err
}

func (f *JSONFile) Save(pretty bool, mode os.FileMode) error {
	if err := f.save(f.filename, pretty, mode); err != nil {
		return err
	}
	f.dirty = false
	return nil
}

// SaveTmp saves the config to a temporary file.  It returns the
// filename and any error.
func (f *JSONFile) SaveTmp(suffix string) (string, error) {
	filename := path.Join(filepath.Dir(f.filename), fmt.Sprintf("keybase_config_%s.json", suffix))
	if err := f.save(filename, true, 0); err != nil {
		return "", err
	}
	return filename, nil
}

func (f *JSONFile) save(filename string, pretty bool, mode os.FileMode) (err error) {
	f.G().Log.Debug("+ saving %s file %s", f.which, filename)

	err = MakeParentDirs(filename)
	if err != nil {
		f.G().Log.Errorf("Failed to make parent dirs for %s", filename)
		return err
	}

	var dat interface{}

	if f.jw == nil {
		// Make a default Dictionary if none already exists
		dat = make(map[string]interface{})
		f.G().Log.Warning("No value for %s file; assuming empty value (i.e., {})",
			f.which)
	} else {
		dat, err = f.jw.GetData()
		if err != nil {
			f.G().Log.Errorf("Failed to encode data for %s file", f.which)
			return err
		}
	}
	var writer *os.File
	flags := (os.O_WRONLY | os.O_CREATE | os.O_TRUNC)
	if mode == 0 {
		mode = PermFile // By default, secrecy
	}
	writer, err = os.OpenFile(filename, flags, mode)
	if err != nil {
		f.G().Log.Errorf("Failed to open %s file %s for writing: %s",
			f.which, filename, err)
		return err
	}
	defer writer.Close()

	if pretty {
		encoded, err := json.MarshalIndent(dat, "", "    ")
		if err == nil {
			_, err = writer.Write(encoded)
		}
	} else {
		encoder := json.NewEncoder(writer)
		err = encoder.Encode(dat)
	}

	if err != nil {
		f.G().Log.Errorf("Error encoding data to %s file %s: %s",
			f.which, filename, err)
		return err
	}

	err = writer.Close()
	if err != nil {
		f.G().Log.Errorf("Error flushing %s file %s: %s", f.which, filename, err)
		return err
	}

	f.G().Log.Debug("Wrote %s file to %s", f.which, filename)

	f.G().Log.Debug("- saved %s file %s", f.which, filename)
	return
}

func (f *JSONFile) SwapTmp(filename string) error {
	if err := MakeParentDirs(f.filename); err != nil {
		return err
	}
	return os.Rename(filename, f.filename)
}
<commit_msg>Add debugging for SwapTmp()<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"

	jsonw "github.com/keybase/go-jsonw"
)

type JSONFile struct {
	filename string
	which    string
	jw       *jsonw.Wrapper
	exists   bool
	dirty    bool
	Contextified
}

func NewJSONFile(g *GlobalContext, filename, which string) *JSONFile {
	return &JSONFile{
		filename:     filename,
		which:        which,
		jw:           jsonw.NewDictionary(),
		Contextified: NewContextified(g),
	}
}

func (f *JSONFile) GetWrapper() *jsonw.Wrapper {
	return f.jw
}
func (f *JSONFile) Exists() bool { return f.exists }

func (f *JSONFile) Load(warnOnNotFound bool) error {
	f.G().Log.Debug("+ loading %s file: %s", f.which, f.filename)
	file, err := os.Open(f.filename)
	if err != nil {
		if os.IsNotExist(err) {
			msg := fmt.Sprintf("No %s file found; tried %s", f.which, f.filename)
			if warnOnNotFound {
				f.G().Log.Warning(msg)
			} else {
				f.G().Log.Debug(msg)
			}
			return nil
		} else if os.IsPermission(err) {
			f.G().Log.Warning("Permission denied opening %s file %s", f.which, f.filename)
			return nil
		} else {
			return err
		}
	}
	f.exists = true
	defer file.Close()
	decoder := json.NewDecoder(file)
	obj := make(map[string]interface{})
	// Treat empty files like an empty dictionary
	if err = decoder.Decode(&obj); err != nil && err != io.EOF {
		f.G().Log.Errorf("Error decoding %s file %s", f.which, f.filename)
		return err
	}
	f.jw = jsonw.NewWrapper(obj)
	f.G().Log.Debug("- successfully loaded %s file", f.which)
	return nil
}

func (f *JSONFile) MaybeSave(pretty bool, mode os.FileMode) (err error) {
	if f != nil && f.dirty {
		err = f.Save(pretty, mode)
	}
	return
}

func (f *JSONFile) Nuke() error {
	f.G().Log.Debug("+ nuke file %s", f.filename)

	err := os.Remove(f.filename)
	f.G().Log.Debug("- nuke file %s -> %s", f.filename, ErrToOk(err))

	return err
}

func (f *JSONFile) Save(pretty bool, mode os.FileMode) error {
	if err := f.save(f.filename, pretty, mode); err != nil {
		return err
	}
	f.dirty = false
	return nil
}

// SaveTmp saves the config to a temporary file.  It returns the
// filename and any error.
func (f *JSONFile) SaveTmp(suffix string) (string, error) {
	filename := path.Join(filepath.Dir(f.filename), fmt.Sprintf("keybase_config_%s.json", suffix))
	if err := f.save(filename, true, 0); err != nil {
		return "", err
	}
	return filename, nil
}

func (f *JSONFile) save(filename string, pretty bool, mode os.FileMode) (err error) {
	f.G().Log.Debug("+ saving %s file %s", f.which, filename)

	err = MakeParentDirs(filename)
	if err != nil {
		f.G().Log.Errorf("Failed to make parent dirs for %s", filename)
		return err
	}

	var dat interface{}

	if f.jw == nil {
		// Make a default Dictionary if none already exists
		dat = make(map[string]interface{})
		f.G().Log.Warning("No value for %s file; assuming empty value (i.e., {})",
			f.which)
	} else {
		dat, err = f.jw.GetData()
		if err != nil {
			f.G().Log.Errorf("Failed to encode data for %s file", f.which)
			return err
		}
	}
	var writer *os.File
	flags := (os.O_WRONLY | os.O_CREATE | os.O_TRUNC)
	if mode == 0 {
		mode = PermFile // By default, secrecy
	}
	writer, err = os.OpenFile(filename, flags, mode)
	if err != nil {
		f.G().Log.Errorf("Failed to open %s file %s for writing: %s",
			f.which, filename, err)
		return err
	}
	defer writer.Close()

	if pretty {
		encoded, err := json.MarshalIndent(dat, "", "    ")
		if err == nil {
			_, err = writer.Write(encoded)
		}
	} else {
		encoder := json.NewEncoder(writer)
		err = encoder.Encode(dat)
	}

	if err != nil {
		f.G().Log.Errorf("Error encoding data to %s file %s: %s",
			f.which, filename, err)
		return err
	}

	err = writer.Close()
	if err != nil {
		f.G().Log.Errorf("Error flushing %s file %s: %s", f.which, filename, err)
		return err
	}

	f.G().Log.Debug("Wrote %s file to %s", f.which, filename)

	f.G().Log.Debug("- saved %s file %s", f.which, filename)
	return
}

func (f *JSONFile) SwapTmp(filename string) (err error) {
	f.G().Log.Debug("+ SwapTmp()")
	defer func() { f.G().Log.Debug("- SwapTmp() -> %s", ErrToOk(err)) }()
	f.G().Log.Debug("| SwapTmp: making parent directories for %q", f.filename)
	if err = MakeParentDirs(f.filename); err != nil {
		return err
	}
	f.G().Log.Debug("| SwapTmp: renaming %q => %q", filename, f.filename)
	err = os.Rename(filename, f.filename)
	return err
}
<|endoftext|>


<commit_before>package godiskcache

import (
	"crypto/sha256"
	"encoding/hex"
	"io/ioutil"
	"log"
	"os"
	"time"
) //import

type GoDiskCache struct {
	Keys map[string]cacheFile
} //struct

type cacheFile struct {
	fileName string
	lifeTime int
} //struct

func New() *GoDiskCache {
	return &GoDiskCache{Keys: make(map[string]cacheFile)}
} //New

func (dc *GoDiskCache) Get(key string) (string, error) {
	var err error

	defer func() {
		if rec := recover(); rec != nil {
			log.Println(rec)
		} //if
	}() //func

	//open the cache file
	if file, err := os.Open(os.TempDir() + dc.Keys[key].fileName); err == nil {
		//get stats about the file, need modified time
		if fi, err := file.Stat(); err == nil {
			//check that cache file is still valid
			if int(time.Now().Sub(fi.ModTime()).Seconds()) < dc.Keys[key].lifeTime {
				//try reading entire file
				if data, err := ioutil.ReadAll(file); err != nil {
					return string(data), err
				} //if
			} //if
		} //if
	} //if

	return "", err
} //Get

func (dc *GoDiskCache) Set(key, data string, lifetime int) error {
	var err error

	defer func() {
		if rec := recover(); rec != nil {
			log.Println(rec)
		} //if
	}() //func

	//convert string to byte slice
	converted := []byte(key)

	//hash the byte slice and return the resulting string
	hasher := sha256.New()
	hasher.Write(converted)
	filename := "godiskcache_" + hex.EncodeToString(hasher.Sum(nil))

	//open the file
	if file, err := os.Create(os.TempDir() + filename); err == nil {
		_, err = file.Write([]byte(data))
		_ = file.Close()
	} //if

	if err == nil {
		dc.Keys[key] = cacheFile{fileName: filename, lifeTime: lifetime}
	} //if

	return err
} //func
<commit_msg>Updated retrieval of file mod time and bug with error check<commit_after>package godiskcache

import (
	"crypto/sha256"
	"encoding/hex"
	"io/ioutil"
	"log"
	"os"
	"time"
) //import

type GoDiskCache struct {
	Keys map[string]cacheFile
} //struct

type cacheFile struct {
	fileName string
	lifeTime int
} //struct

func New() *GoDiskCache {
	return &GoDiskCache{Keys: make(map[string]cacheFile)}
} //New

func (dc *GoDiskCache) Get(key string) (string, error) {
	var err error

	defer func() {
		if rec := recover(); rec != nil {
			log.Println(rec)
		} //if
	}() //func

	//open the cache file
	if file, err := os.Open(os.TempDir() + dc.Keys[key].fileName); err == nil {
		//get stats about the file, need modified time
		if fi, err := file.Stat(); err == nil {
			//check that cache file is still valid
			if int(time.Since(fi.ModTime()).Seconds()) < dc.Keys[key].lifeTime {
				//try reading entire file
				if data, err := ioutil.ReadAll(file); err == nil {
					return string(data), err
				} //if
			} //if
		} //if
	} //if

	return "", err
} //Get

func (dc *GoDiskCache) Set(key, data string, lifetime int) error {
	var err error

	defer func() {
		if rec := recover(); rec != nil {
			log.Println(rec)
		} //if
	}() //func

	//convert string to byte slice
	converted := []byte(key)

	//hash the byte slice and return the resulting string
	hasher := sha256.New()
	hasher.Write(converted)
	filename := "godiskcache_" + hex.EncodeToString(hasher.Sum(nil))

	//open the file
	if file, err := os.Create(os.TempDir() + filename); err == nil {
		_, err = file.Write([]byte(data))
		_ = file.Close()
	} //if

	if err == nil {
		dc.Keys[key] = cacheFile{fileName: filename, lifeTime: lifetime}
	} //if

	return err
} //func
<|endoftext|>


<commit_before>package goisilon

import (
	"flag"
	"os"
	"testing"

	"github.com/Sirupsen/logrus"
	log "github.com/codedellemc/gournal"
	glogrus "github.com/codedellemc/gournal/logrus"
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var (
	err        error
	client     *Client
	defaultCtx context.Context
)

func init() {
	defaultCtx = context.Background()
	defaultCtx = context.WithValue(
		defaultCtx,
		log.AppenderKey(),
		glogrus.NewWithOptions(
			logrus.StandardLogger().Out,
			logrus.DebugLevel,
			logrus.StandardLogger().Formatter))
}

func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Verbose() {
		defaultCtx = context.WithValue(
			defaultCtx,
			log.LevelKey(),
			log.DebugLevel)
	}

	client, err = NewClient(defaultCtx)
	if err != nil {
		log.WithError(err).Panic(defaultCtx, "error creating test client")
	}
	os.Exit(m.Run())
}

func assertLen(t *testing.T, obj interface{}, expLen int) {
	if !assert.Len(t, obj, expLen) {
		t.FailNow()
	}
}

func assertError(t *testing.T, err error) {
	if !assert.Error(t, err) {
		t.FailNow()
	}
}

func assertNoError(t *testing.T, err error) {
	if !assert.NoError(t, err) {
		t.FailNow()
	}
}

func assertNil(t *testing.T, i interface{}) {
	if !assert.Nil(t, i) {
		t.FailNow()
	}
}

func assertNotNil(t *testing.T, i interface{}) {
	if !assert.NotNil(t, i) {
		t.FailNow()
	}
}
<commit_msg>rename Sirupsen/logrus to sirupsen/logrus<commit_after>package goisilon

import (
	"flag"
	"os"
	"testing"

	log "github.com/codedellemc/gournal"
	glogrus "github.com/codedellemc/gournal/logrus"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var (
	err        error
	client     *Client
	defaultCtx context.Context
)

func init() {
	defaultCtx = context.Background()
	defaultCtx = context.WithValue(
		defaultCtx,
		log.AppenderKey(),
		glogrus.NewWithOptions(
			logrus.StandardLogger().Out,
			logrus.DebugLevel,
			logrus.StandardLogger().Formatter))
}

func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Verbose() {
		defaultCtx = context.WithValue(
			defaultCtx,
			log.LevelKey(),
			log.DebugLevel)
	}

	client, err = NewClient(defaultCtx)
	if err != nil {
		log.WithError(err).Panic(defaultCtx, "error creating test client")
	}
	os.Exit(m.Run())
}

func assertLen(t *testing.T, obj interface{}, expLen int) {
	if !assert.Len(t, obj, expLen) {
		t.FailNow()
	}
}

func assertError(t *testing.T, err error) {
	if !assert.Error(t, err) {
		t.FailNow()
	}
}

func assertNoError(t *testing.T, err error) {
	if !assert.NoError(t, err) {
		t.FailNow()
	}
}

func assertNil(t *testing.T, i interface{}) {
	if !assert.Nil(t, i) {
		t.FailNow()
	}
}

func assertNotNil(t *testing.T, i interface{}) {
	if !assert.NotNil(t, i) {
		t.FailNow()
	}
}
<|endoftext|>


<commit_before>package siteengines

import (
	"strconv"
	"time"

	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
	"github.com/xyproto/simpleredis"
	"github.com/xyproto/web"
)

// An Engine is a specific piece of a website
// This part handles the "chat" pages

type ChatEngine struct {
	userState *UserState
	chatState *ChatState
}

type ChatState struct {
	active   *simpleredis.Set            // A list of all users that are in the chat, must correspond to the users in UserState.users
	said     *simpleredis.List           // A list of everything that has been said so far
	userInfo *simpleredis.HashMap        // Info about a chat user - last seen, preferred number of lines etc
	pool     *simpleredis.ConnectionPool // A connection pool for Redis
}

func NewChatEngine(userState *UserState) *ChatEngine {
	pool := userState.GetPool()
	chatState := new(ChatState)
	chatState.active = simpleredis.NewSet(pool, "active")
	chatState.said = simpleredis.NewList(pool, "said")
	chatState.userInfo = simpleredis.NewHashMap(pool, "userInfo") // lastSeen.time is an encoded timestamp for when the user was last seen chatting
	chatState.pool = pool
	return &ChatEngine{userState, chatState}
}

func (ce *ChatEngine) ServePages(basecp BaseCP, menuEntries MenuEntries) {
	chatCP := basecp(ce.userState)
	chatCP.ContentTitle = "Chat"
	chatCP.ExtraCSSurls = append(chatCP.ExtraCSSurls, "/css/chat.css")

	tvgf := DynamicMenuFactoryGenerator(menuEntries)
	tvg := tvgf(ce.userState)

	web.Get("/chat", chatCP.WrapSimpleContextHandle(ce.GenerateChatCurrentUser(), tvg))
	web.Post("/say", ce.GenerateSayCurrentUser())
	web.Get("/css/chat.css", ce.GenerateCSS(chatCP.ColorScheme))
	web.Post("/setchatlines", ce.GenerateSetChatLinesCurrentUser())
	// For debugging
	web.Get("/getchatlines", ce.GenerateGetChatLinesCurrentUser())
}

func (ce *ChatEngine) SetLines(username string, lines int) {
	ce.chatState.userInfo.Set(username, "lines", strconv.Itoa(lines))
}

func (ce *ChatEngine) GetLines(username string) int {
	val, err := ce.chatState.userInfo.Get(username, "lines")
	if err != nil {
		// The default
		return 20
	}
	num, err := strconv.Atoi(val)
	if err != nil {
		// The default
		return 20
	}
	return num
}

// Mark a user as seen
func (ce *ChatEngine) Seen(username string) {
	now := time.Now()
	encodedTime, err := now.GobEncode()
	if err != nil {
		panic("ERROR: Can't encode the time")
	}
	ce.chatState.userInfo.Set(username, "lastseen", string(encodedTime))
}

// Checks if the user has been seen lately (within 12 hours ago)
func (ce *ChatEngine) SeenLately(username string) bool {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err != nil {
		return false
	}
	var then time.Time
	err = then.GobDecode([]byte(encodedTime))
	if err != nil {
		return false
	}
	notTooLongDuration, err := time.ParseDuration("-12h")
	if err != nil {
		return false
	}
	notTooLongAgo := time.Now().Add(notTooLongDuration)
	if then.After(notTooLongAgo) {
		return true
	}
	return false
}

func (ce *ChatEngine) GetLastSeen(username string) string {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err == nil {
		var then time.Time
		err = then.GobDecode([]byte(encodedTime))
		if err == nil {
			timestamp := then.String()
			return timestamp[11:19]
		}
	}
	return "never"
}

func (ce *ChatEngine) IsChatting(username string) bool {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err == nil {
		var then time.Time
		err = then.GobDecode([]byte(encodedTime))
		if err == nil {
			elapsed := time.Since(then)
			if elapsed.Minutes() > 20 {
				// 20 minutes since last seen saying anything, set as not chatting
				ce.SetChatting(username, false)
				return false
			}
		}
	}
	// TODO: If the user was last seen more than N minutes ago, set as not chatting and return false
	return ce.userState.GetBooleanField(username, "chatting")
}

// Set "chatting" to "true" or "false" for a given user
func (ce *ChatEngine) SetChatting(username string, val bool) {
	ce.userState.SetBooleanField(username, "chatting", val)
}

func (ce *ChatEngine) JoinChat(username string) {
	// Join the chat
	ce.chatState.active.Add(username)
	// Change the chat status for the user
	ce.SetChatting(username, true)
	// Mark the user as seen
	ce.Seen(username)
}

func (ce *ChatEngine) Say(username, text string) {
	timestamp := time.Now().String()
	textline := timestamp[11:19] + "  " + username + "> " + text
	ce.chatState.said.Add(textline)
	// Store the timestamp for when the user was last seen as well
	ce.Seen(username)
}

func LeaveChat(ce *ChatEngine, username string) {
	// Leave the chat
	ce.chatState.active.Del(username)
	// Change the chat status for the user
	ce.SetChatting(username, false)
}

func (ce *ChatEngine) GetChatUsers() []string {
	chatUsernames, err := ce.chatState.active.GetAll()
	if err != nil {
		return []string{}
	}
	return chatUsernames
}

func (ce *ChatEngine) GetChatText() []string {
	chatText, err := ce.chatState.said.GetAll()
	if err != nil {
		return []string{}
	}
	return chatText
}

// Get the last N entries
func (ce *ChatEngine) GetLastChatText(n int) []string {
	chatText, err := ce.chatState.said.GetLastN(n)
	if err != nil {
		return []string{}
	}
	return chatText
}

func (ce *ChatEngine) chatText(lines int) string {
	if lines == -1 {
		return ""
	}
	retval := "
"
	// Show N lines of chat text
	for _, said := range ce.GetLastChatText(lines) {
		retval += said + "
"
	}
	return retval + "
"
}

func (ce *ChatEngine) GenerateChatCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}

		ce.JoinChat(username)

		// TODO: Add a button for someone to see the entire chat
		// TODO: Add some protection against random monkeys that only fling poo

		retval := "
Hi " + username + "
"
		seenusers := ""
		for _, otherUser := range ce.GetChatUsers() {
			if otherUser == username {
				continue
			}
			if ce.SeenLately(otherUser) {
				seenusers += "  " + otherUser + ", last seen " + ce.GetLastSeen(otherUser) + "
"
			}
		}
		// Add a list of participants that has been seen lately, if there are any
		if seenusers != "" {
			retval += "
Other participants:
"
			retval += seenusers
			retval += "
"
		}
		retval += "
"
		retval += ce.chatText(ce.GetLines(username))
		retval += "
"
		retval += "
"
		retval += JS("var fastestPolling = 500;")
		retval += JS("var slowestPolling = 64000;")
		retval += JS("var pollInterval = fastestPolling;")
		retval += JS("var inactivityCounter = 0;")
		retval += JS("var inactivityTimeout = 20;") // Chat times out after 20 periods of slowest polling (approximately 20 minutes)
		retval += JS("var pollID = 0;")
		// The say() function for submitting text over ajax (a post request), clearing the text intput field and updating the chat text.
		// Also sets the polling interval to the fastest value.
		retval += JS(`function say(text) {
			inactivityCounter = 0;
			pollInterval = fastestPolling;
			$.post('/say', {said:$('#sayText').val()}, function(data) { $('#sayText').val(''); $('#chatText').html(data); });
		}`)
		// Call say() at return
		retval += "<input size='60' id='sayText' name='said' type='text' onKeypress=\"if (event.keyCode == 13) { say($('#sayText').val()); };\">"
		// Cal say() at the click of the button
		retval += "<button onClick='say();'>Say</button>"
		// Focus on the text input
		retval += JS(Focus("#sayText"))
		// Update the chat text. Reduce the poll interval at every poll.
		// When the user does something, the polling interval will be reset to something quicker.
		retval += JS(`function UpdateChat() {
		    if (pollInterval < slowestPolling) {
			    pollInterval *= 2;
				clearInterval(pollID);
				pollID = setInterval(UpdateChat, pollInterval);
			} else {
				inactivityCounter++;
			}
			if inactivityCounter < inactivityTimeout {
				$.post('/say', {}, function(data) { $('#chatText').html(data); });
			}
		}`)
		retval += JS("pollID = setInterval(UpdateChat, pollInterval);")
		// A function for setting the preferred number of lines
		retval += JS("function setlines(numlines) { $.post('/setchatlines', {lines:numlines}, function(data) { $('#chatText').html(data); " + ScrollDownAnimated() + "}); }")
		// A button for viewing 20 lines at a time
		retval += "<button onClick='setlines(20);'>20</button>"
		// A button for viewing 50 lines at a time
		retval += "<button onClick='setlines(50);'>50</button>"
		// A button for viewing 99999 lines at a time
		retval += "<button onClick='setlines(99999);'>99999</button>"
		// For viewing all the text so far

		return retval
	}
}

func (ce *ChatEngine) GenerateSayCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		said, found := ctx.Params["said"]
		if !found || said == "" {
			// Return the text instead of giving an error for easy use of /say to refresh the content
			// Note that as long as Say below isn't called, the user will be marked as inactive eventually
			return ce.chatText(ce.GetLines(username))
		}

		ce.Say(username, CleanUserInput(said))

		return ce.chatText(ce.GetLines(username))
	}
}

func (ce *ChatEngine) GenerateGetChatLinesCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		num := ce.GetLines(username)

		return strconv.Itoa(num)
	}
}

func (ce *ChatEngine) GenerateSetChatLinesCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		lines, found := ctx.Params["lines"]
		if !found || lines == "" {
			return instapage.MessageOKback("Set chat lines", "Missing value for preferred number of lines")
		}
		num, err := strconv.Atoi(lines)
		if err != nil {
			return instapage.MessageOKback("Set chat lines", "Invalid number of lines: "+lines)
		}

		// Set the preferred number of lines for this user
		ce.SetLines(username, num)

		return ce.chatText(num)
	}
}

func (ce *ChatEngine) GenerateCSS(cs *ColorScheme) SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")
		return `
.yes {
	background-color: #90ff90;
	color: black;
}
.no {
	background-color: #ff9090;
	color: black;
}

.username:link { color: green; }
.username:visited { color: green; }
.username:hover { color: green; }
.username:active { color: green; }

.whitebg {
	background-color: white;
}

.darkgrey:link { color: #404040; }
.darkgrey:visited { color: #404040; }
.darkgrey:hover { color: #404040; }
.darkgrey:active { color: #404040; }

#chatText {
	background-color: white;
}

`
		//
	}
}
<commit_msg>Adjustments to the timeout js code<commit_after>package siteengines

import (
	"strconv"
	"time"

	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
	"github.com/xyproto/simpleredis"
	"github.com/xyproto/web"
)

// An Engine is a specific piece of a website
// This part handles the "chat" pages

type ChatEngine struct {
	userState *UserState
	chatState *ChatState
}

type ChatState struct {
	active   *simpleredis.Set            // A list of all users that are in the chat, must correspond to the users in UserState.users
	said     *simpleredis.List           // A list of everything that has been said so far
	userInfo *simpleredis.HashMap        // Info about a chat user - last seen, preferred number of lines etc
	pool     *simpleredis.ConnectionPool // A connection pool for Redis
}

func NewChatEngine(userState *UserState) *ChatEngine {
	pool := userState.GetPool()
	chatState := new(ChatState)
	chatState.active = simpleredis.NewSet(pool, "active")
	chatState.said = simpleredis.NewList(pool, "said")
	chatState.userInfo = simpleredis.NewHashMap(pool, "userInfo") // lastSeen.time is an encoded timestamp for when the user was last seen chatting
	chatState.pool = pool
	return &ChatEngine{userState, chatState}
}

func (ce *ChatEngine) ServePages(basecp BaseCP, menuEntries MenuEntries) {
	chatCP := basecp(ce.userState)
	chatCP.ContentTitle = "Chat"
	chatCP.ExtraCSSurls = append(chatCP.ExtraCSSurls, "/css/chat.css")

	tvgf := DynamicMenuFactoryGenerator(menuEntries)
	tvg := tvgf(ce.userState)

	web.Get("/chat", chatCP.WrapSimpleContextHandle(ce.GenerateChatCurrentUser(), tvg))
	web.Post("/say", ce.GenerateSayCurrentUser())
	web.Get("/css/chat.css", ce.GenerateCSS(chatCP.ColorScheme))
	web.Post("/setchatlines", ce.GenerateSetChatLinesCurrentUser())
	// For debugging
	web.Get("/getchatlines", ce.GenerateGetChatLinesCurrentUser())
}

func (ce *ChatEngine) SetLines(username string, lines int) {
	ce.chatState.userInfo.Set(username, "lines", strconv.Itoa(lines))
}

func (ce *ChatEngine) GetLines(username string) int {
	val, err := ce.chatState.userInfo.Get(username, "lines")
	if err != nil {
		// The default
		return 20
	}
	num, err := strconv.Atoi(val)
	if err != nil {
		// The default
		return 20
	}
	return num
}

// Mark a user as seen
func (ce *ChatEngine) Seen(username string) {
	now := time.Now()
	encodedTime, err := now.GobEncode()
	if err != nil {
		panic("ERROR: Can't encode the time")
	}
	ce.chatState.userInfo.Set(username, "lastseen", string(encodedTime))
}

// Checks if the user has been seen lately (within 12 hours ago)
func (ce *ChatEngine) SeenLately(username string) bool {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err != nil {
		return false
	}
	var then time.Time
	err = then.GobDecode([]byte(encodedTime))
	if err != nil {
		return false
	}
	notTooLongDuration, err := time.ParseDuration("-12h")
	if err != nil {
		return false
	}
	notTooLongAgo := time.Now().Add(notTooLongDuration)
	if then.After(notTooLongAgo) {
		return true
	}
	return false
}

func (ce *ChatEngine) GetLastSeen(username string) string {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err == nil {
		var then time.Time
		err = then.GobDecode([]byte(encodedTime))
		if err == nil {
			timestamp := then.String()
			return timestamp[11:19]
		}
	}
	return "never"
}

func (ce *ChatEngine) IsChatting(username string) bool {
	encodedTime, err := ce.chatState.userInfo.Get(username, "lastseen")
	if err == nil {
		var then time.Time
		err = then.GobDecode([]byte(encodedTime))
		if err == nil {
			elapsed := time.Since(then)
			if elapsed.Minutes() > 20 {
				// 20 minutes since last seen saying anything, set as not chatting
				ce.SetChatting(username, false)
				return false
			}
		}
	}
	// TODO: If the user was last seen more than N minutes ago, set as not chatting and return false
	return ce.userState.GetBooleanField(username, "chatting")
}

// Set "chatting" to "true" or "false" for a given user
func (ce *ChatEngine) SetChatting(username string, val bool) {
	ce.userState.SetBooleanField(username, "chatting", val)
}

func (ce *ChatEngine) JoinChat(username string) {
	// Join the chat
	ce.chatState.active.Add(username)
	// Change the chat status for the user
	ce.SetChatting(username, true)
	// Mark the user as seen
	ce.Seen(username)
}

func (ce *ChatEngine) Say(username, text string) {
	timestamp := time.Now().String()
	textline := timestamp[11:19] + "  " + username + "> " + text
	ce.chatState.said.Add(textline)
	// Store the timestamp for when the user was last seen as well
	ce.Seen(username)
}

func LeaveChat(ce *ChatEngine, username string) {
	// Leave the chat
	ce.chatState.active.Del(username)
	// Change the chat status for the user
	ce.SetChatting(username, false)
}

func (ce *ChatEngine) GetChatUsers() []string {
	chatUsernames, err := ce.chatState.active.GetAll()
	if err != nil {
		return []string{}
	}
	return chatUsernames
}

func (ce *ChatEngine) GetChatText() []string {
	chatText, err := ce.chatState.said.GetAll()
	if err != nil {
		return []string{}
	}
	return chatText
}

// Get the last N entries
func (ce *ChatEngine) GetLastChatText(n int) []string {
	chatText, err := ce.chatState.said.GetLastN(n)
	if err != nil {
		return []string{}
	}
	return chatText
}

func (ce *ChatEngine) chatText(lines int) string {
	if lines == -1 {
		return ""
	}
	retval := "
"
	// Show N lines of chat text
	for _, said := range ce.GetLastChatText(lines) {
		retval += said + "
"
	}
	return retval + "
"
}

func (ce *ChatEngine) GenerateChatCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}

		ce.JoinChat(username)

		// TODO: Add a button for someone to see the entire chat
		// TODO: Add some protection against random monkeys that only fling poo

		retval := "
Hi " + username + "
"
		seenusers := ""
		for _, otherUser := range ce.GetChatUsers() {
			if otherUser == username {
				continue
			}
			if ce.SeenLately(otherUser) {
				seenusers += "  " + otherUser + ", last seen " + ce.GetLastSeen(otherUser) + "
"
			}
		}
		// Add a list of participants that has been seen lately, if there are any
		if seenusers != "" {
			retval += "
Other participants:
"
			retval += seenusers
			retval += "
"
		}
		retval += "
"
		retval += ce.chatText(ce.GetLines(username))
		retval += "
"
		retval += "
"
		retval += JS("var fastestPolling = 400;")
		retval += JS("var slowestPolling = 64000;")
		retval += JS("var pollInterval = fastestPolling;")
		retval += JS("var pollID = 0;")
		// The say() function for submitting text over ajax (a post request), clearing the text intput field and updating the chat text.
		// Also sets the polling interval to the fastest value.
		retval += JS(`function say(text) {
			inactivityCounter = 0;
			pollInterval = fastestPolling;
			$.post('/say', {said:$('#sayText').val()}, function(data) { $('#sayText').val(''); $('#chatText').html(data); });
		}`)
		// Call say() at return
		retval += "<input size='60' id='sayText' name='said' type='text' onKeypress=\"if (event.keyCode == 13) { say($('#sayText').val()); };\">"
		// Cal say() at the click of the button
		retval += "<button onClick='say();'>Say</button>"
		// Focus on the text input
		retval += JS(Focus("#sayText"))
		// Update the chat text. Reduce the poll interval at every poll.
		// When the user does something, the polling interval will be reset to something quicker.
		retval += JS(`function UpdateChat() {
		    if (pollInterval < slowestPolling) {
			    pollInterval *= 2;
				clearInterval(pollID);
				pollID = setInterval(UpdateChat, pollInterval);
			}
			$.post('/say', {}, function(data) { $('#chatText').html(data); });
		}`)
		retval += JS("pollID = setInterval(UpdateChat, pollInterval);")
		// A function for setting the preferred number of lines
		retval += JS("function setlines(numlines) { $.post('/setchatlines', {lines:numlines}, function(data) { $('#chatText').html(data); " + ScrollDownAnimated() + "}); }")
		// A button for viewing 20 lines at a time
		retval += "<button onClick='setlines(20);'>20</button>"
		// A button for viewing 50 lines at a time
		retval += "<button onClick='setlines(50);'>50</button>"
		// A button for viewing 99999 lines at a time
		retval += "<button onClick='setlines(99999);'>99999</button>"
		// For viewing all the text so far

		return retval
	}
}

func (ce *ChatEngine) GenerateSayCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		said, found := ctx.Params["said"]
		if !found || said == "" {
			// Return the text instead of giving an error for easy use of /say to refresh the content
			// Note that as long as Say below isn't called, the user will be marked as inactive eventually
			return ce.chatText(ce.GetLines(username))
		}

		ce.Say(username, CleanUserInput(said))

		return ce.chatText(ce.GetLines(username))
	}
}

func (ce *ChatEngine) GenerateGetChatLinesCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		num := ce.GetLines(username)

		return strconv.Itoa(num)
	}
}

func (ce *ChatEngine) GenerateSetChatLinesCurrentUser() SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return "No user logged in"
		}
		if !ce.userState.IsLoggedIn(username) {
			return "Not logged in"
		}
		if !ce.IsChatting(username) {
			return "Not currently chatting"
		}
		lines, found := ctx.Params["lines"]
		if !found || lines == "" {
			return instapage.MessageOKback("Set chat lines", "Missing value for preferred number of lines")
		}
		num, err := strconv.Atoi(lines)
		if err != nil {
			return instapage.MessageOKback("Set chat lines", "Invalid number of lines: "+lines)
		}

		// Set the preferred number of lines for this user
		ce.SetLines(username, num)

		return ce.chatText(num)
	}
}

func (ce *ChatEngine) GenerateCSS(cs *ColorScheme) SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")
		return `
.yes {
	background-color: #90ff90;
	color: black;
}
.no {
	background-color: #ff9090;
	color: black;
}

.username:link { color: green; }
.username:visited { color: green; }
.username:hover { color: green; }
.username:active { color: green; }

.whitebg {
	background-color: white;
}

.darkgrey:link { color: #404040; }
.darkgrey:visited { color: #404040; }
.darkgrey:hover { color: #404040; }
.darkgrey:active { color: #404040; }

#chatText {
	background-color: white;
}

`
		//
	}
}
<|endoftext|>


<commit_before>package main // import "github.com/qadium/plumber/manager"

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
)

func forwardData(dest string, body io.ReadCloser) (io.ReadCloser, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", dest, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req) // this will close the body, too
	if err != nil {
		return nil, err
	}
	return resp.Body, nil
}

func createHandler(args []string) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		var err error
		if r.Body == nil || r.Method != "POST" {
			http.NotFound(w, r)
		} else {
			body := r.Body
			defer body.Close()

			for _, host := range args {
				body, err = forwardData(host, body)
				if err != nil {
					panic(err)
				}
			}

			final, err := ioutil.ReadAll(io.LimitReader(body, 1048576))
			if err != nil {
				panic(err)
			}

			w.Header().Set("Content-Type", "application/json")
			w.Write(final)
		}
	}
}

func main() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	go func() {
		<-c
		log.Printf("Received termination; qutting.")
		// by setting the exit status to 0, we don't cause any parent
		// processes to think this was an unexpected termination
		os.Exit(0)
	}()

	http.HandleFunc("/", createHandler(os.Args[1:]))
	log.Fatal(http.ListenAndServe(":9800", nil))
}
<commit_msg>updated manager<commit_after>package main // import "github.com/qadium/plumber/manager"

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"net"
	"net/url"
)

func forwardData(dest string, body io.ReadCloser) (io.ReadCloser, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", dest, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req) // this will close the body, too
	if err != nil {
		return nil, err
	}
	return resp.Body, nil
}

func createHandler(args []string) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		var err error
		if r.Body == nil || r.Method != "POST" {
			http.NotFound(w, r)
		} else {
			body := r.Body
			defer body.Close()

			for _, host := range args {
				body, err = forwardData(host, body)
				if err != nil {
					panic(err)
				}
			}

			final, err := ioutil.ReadAll(io.LimitReader(body, 1048576))
			if err != nil {
				panic(err)
			}

			w.Header().Set("Content-Type", "application/json")
			w.Write(final)
		}
	}
}

func main() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listener, err := net.Listen("tcp", ":9800")
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}

	go func() {
		<-c
		log.Printf("Received termination; qutting.")
		// by setting the exit status to 0, we don't cause any parent
		// processes to think this was an unexpected termination
		listener.Close()
	}()

	// sanitize args to make sure they contain valid urls
	args := []string{}
	for _, arg := range os.Args[1:] {
		parsedUrl, err := url.Parse(arg)
		if err == nil && parsedUrl.Scheme == "http" {
			args = append(args, arg)
		} else {
			log.Printf("'%v' is not a valid url, discarding", arg)
		}
	}
	log.Printf("Forwarding JSONs to '%v'.", args)

	http.HandleFunc("/", createHandler(args))
	http.Serve(listener, nil)
}
<|endoftext|>


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

/*
Package client provides clients for accessing the various
externally-facing Cockroach database endpoints.

TODO(pmattis): Update these docs to talk about the DB interface.

KV Client

The KV client is a fully-featured client of Cockroach's key-value
database. It provides a simple, synchronous interface well-suited to
parallel updates and queries.

The simplest way to use the client is through the Run method. Run
synchronously invokes the call and fills in the the reply and returns
an error. The example below shows a get and a put.

  kv := client.newKV(nil, client.newHTTPSender("localhost:8080", httpClient))

  getCall := client.Get(proto.Key("a"))
  getResp := getCall.Reply.(*proto.GetResponse)
  if err := kv.Run(getCall); err != nil {
    log.Fatal(err)
  }
  if err := kv.Run(client.Put(proto.Key("b"), getResp.Value.Bytes)) err != nil {
    log.Fatal(err)
  }

The API is synchronous, but accommodates efficient parallel updates
and queries using the variadic Run method. An arbitrary number of
calls may be passed to Run which are sent to Cockroach as part of a
batch. Note however that such the individual API calls within a batch
are not guaranteed to have atomic semantics. A transaction must be
used to guarantee atomicity. A simple example of using the API which
does two scans in parallel and then sends a sequence of puts in
parallel:

  kv := client.newKV(nil, client.newHTTPSender("localhost:8080", httpClient))

  acScan := client.Scan(proto.Key("a"), proto.Key("c\x00"), 1000)
  xzScan := client.Scan(proto.Key("x"), proto.Key("z\x00"), 1000)

  // Run sends both scans in parallel and returns first error or nil.
  if err := kv.Run(acScan, xzScan); err != nil {
    log.Fatal(err)
  }

  acResp := acScan.Reply.(*proto.ScanResponse)
  xzResp := xzScan.Reply.(*proto.ScanResponse)

  // Append maximum value from "a"-"c" to all values from "x"-"z".
  max := []byte(nil)
  for _, keyVal := range acResp.Rows {
    if bytes.Compare(max, keyVal.Value.Bytes) < 0 {
      max = keyVal.Value.Bytes
    }
  }
  var calls []*client.Call
  for keyVal := range xzResp.Rows {
    putCall := client.Put(keyVal.Key, bytes.Join([][]byte{keyVal.Value.Bytes, max}, []byte(nil)))
    calls = append(calls, putCall)
  }

  // Run all puts for parallel execution.
  if err := kv.Run(calls...); err != nil {
    log.Fatal(err)
  }

Transactions are supported through the RunTransaction() method, which
takes a retryable function, itself composed of the same simple mix of
API calls typical of a non-transactional operation. Within the context
of the RunTransaction call, all method invocations are transparently
given necessary transactional details, and conflicts are handled with
backoff/retry loops and transaction restarts as necessary. An example
of using transactions with parallel writes:

  kv := client.newKV(nil, client.newHTTPSender("localhost:8080", httpClient))

  opts := &client.transactionOptions{Name: "test", Isolation: proto.SERIALIZABLE}
  err := kv.RunTransaction(opts, func(txn *client.Txn) error {
    for i := 0; i < 100; i++ {
      key := proto.Key(fmt.Sprintf("testkey-%02d", i))
      txn.Prepare(client.Put(key, []byte("test value")))
    }

    // Note that the Txn client is flushed automatically on transaction
    // commit. Invoking Flush after individual API methods is only
    // required if the result needs to be received to take conditional
    // action.
    return nil
  })
  if err != nil {
    log.Fatal(err)
  }

Note that with Cockroach's lock-free transactions, clients should
expect retries as a matter of course. This is why the transaction
functionality is exposed through a retryable function. The retryable
function should have no side effects which are not idempotent.

Transactions should endeavor to write using KV.Prepare calls. This
allows writes to the same range to be batched together. In cases where
the entire transaction affects only a single range, transactions can
commit in a single round trip.
*/
package client
<commit_msg>Update client package documentation.<commit_after>// Copyright 2014 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

/*
Package client provides clients for accessing the various externally-facing
Cockroach database endpoints.

DB Client

The DB client is a fully-featured client of Cockroach's key-value database. It
provides a simple, synchronous interface well-suited to parallel updates and
queries.

The simplest way to use the client is through the Run method. Run synchronously
invokes the call, fills in the the reply and returns an error. The example
below shows a get and a put.

	db, err := client.Open("https://root@localhost:8080")
	if err != nil {
		log.Fatal(err)
	}
	if err := db.Put("a", "hello"); err != nil {
		log.Fatal(err)
	}
	if gr, err := db.Get("a"); err != nil {
		log.Fatal(err)
	} else {
		log.Printf("%s", gr.ValueBytes())  // "hello"
	}

The API is synchronous, but accommodates efficient parallel updates and queries
using Batch objects. An arbitrary number of calls may be added to a Batch which
is executed using DB.Run. Note however that the individual calls within a batch
are not guaranteed to have atomic semantics. A transaction must be used to
guarantee atomicity. A simple example of using a Batch which does two scans in
parallel and then sends a sequence of puts in parallel:

	db, err := client.Open("https://root@localhost:8080")
	if err != nil {
		log.Fatal(err)
	}

	b1 := &client.Batch{}
	b1.Scan("a", "c\x00", 1000)
	b1.Scan("x", "z\x00", 1000)

	// Run sends both scans in parallel and returns the first error or nil.
	if err := db.Run(b1); err != nil {
		log.Fatal(err)
	}

	acResult := b1.Results[0]
	xzResult := b1.Results[1]

	// Append maximum value from "a"-"c" to all values from "x"-"z".
	max := []byte(nil)
	for _, row := range acResult.Rows {
		if bytes.Compare(max, row.ValueBytes()) < 0 {
			max = row.ValueBytes()
		}
	}

	b2 := &client.Batch{}
	for _, row := range xzResult.Rows {
		b2.Put(row.Key, bytes.Join([][]byte{row.ValueBytes(), max}, []byte(nil)))
	}

	// Run all puts for parallel execution.
	if err := db.Run(b2); err != nil {
		log.Fatal(err)
	}

Transactions are supported through the DB.Txn() method, which takes a retryable
function, itself composed of the same simple mix of API calls typical of a
non-transactional operation. Within the context of the Txn() call, all method
invocations are transparently given necessary transactional details, and
conflicts are handled with backoff/retry loops and transaction restarts as
necessary. An example of using transactions with parallel writes:

	db, err := client.Open("https://root@localhost:8080")
	if err != nil {
		log.Fatal(err)
	}

	err := db.Txn(func(txn *client.Txn) error {
		b := &client.Batch{}
		for i := 0; i < 100; i++ {
			key := fmt.Sprintf("testkey-%02d", i)
			b.Put(key, "test value")
		}

		// Note that the Txn client is flushed automatically when this function
		// returns success (i.e. nil). Calling Commit explicitly can sometimes
		// reduce the number of RPCs.
		return txn.Commit(b)
	})
	if err != nil {
		log.Fatal(err)
	}

Note that with Cockroach's lock-free transactions, clients should expect
retries as a matter of course. This is why the transaction functionality is
exposed through a retryable function. The retryable function should have no
side effects which are not idempotent.

Transactions should endeavor to use batches to perform multiple operations in a
single RPC. In addition to the reduced number of RPCs to the server, this
allows writes to the same range to be batched together. In cases where the
entire transaction affects only a single range, transactions can commit in a
single round trip.
*/
package client
<|endoftext|>


<commit_before>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/jquery"
)

// AjaxError is returned on the error channel after a call to an Ajax method.
type AjaxError struct {
	StatusCode int
	Message    string
}

//AjaxPut behaves indentically to AjaxPost other than using the method PUT.
func AjaxPut(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "PUT", true)
}

//AjaxDelete behaves indentically to AjaxPost other than using the method DELETE
//and not sending the object to be deleted's contents, just its id. First parameter
//here is just for the type.
func AjaxDelete(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "DELETE", false)
}

//AjaxPost sends an instance of a wire type to the server.  The first argument
//should be a wire type and must be a pointer to a struct or this function
//will panic. The value sent to the server is supplied in the first argument.
//The two returned values are a content channel and an error channel.  If the
//call succeeds, the content channel will be sent a different instance of the
//same type as the first argument. If the result from the server cannot be understood
//as the type of the first argument, the special error code 418 will be sent
//on the error channel.  If we fail to encode the object to be sent, the error
//code 420 will be sent on the error channel and no call to the server is made.
func AjaxPost(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "POST", true)
}

//AjaxIndex retreives a collection of wire types from the server.
//If the first argument is not a pointer to a slice of pointer to struct,
//it will panic.  The first element should be a slice of wire types.
//The returned values are a content channel and an error channel.  The content
//channel will receive the same type as your first argument if anything.  The error
//channel is used for non-200 http responses and the special error code 418
//is used to indicate that the received json from the server could not be successfully
//parsed as the type of the first argument.
func AjaxIndex(ptrToSliceOfPtrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	isPointerToSliceOfPointerToStructOrPanic(ptrToSliceOfPtrToStruct)
	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)
	ajaxRawChannels(ptrToSliceOfPtrToStruct, "", contentCh, errCh, "GET", path)
	return contentCh, errCh
}

//AjaxGet retreives an instance of a wire type from the server and decodes the result as
//Json. If the first argument is not a pointer to a struct, it will panic.
//The first argument should be a wire type that you expect to receive in the success
//case.  The returned values are a content channel and an error channel.  The content
//channel will receive the same type as your first argument if anything.  The error
//channel is used for non-200 http responses and the special error code 418
//is used to indicate that the received json from the server could not be successfully
//parsed as the type of the first argument.
func AjaxGet(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	isPointerToStructOrPanic(ptrToStruct)
	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)
	ajaxRawChannels(ptrToStruct, "", contentCh, errCh, "GET", path)
	return contentCh, errCh
}

func ajaxRawChannels(output interface{}, body string, contentChan chan interface{}, errChan chan AjaxError,
	method string, path string) error {

	m := map[string]interface{}{
		"contentType": "application/json",
		"dataType":    "text",
		"type":        method,
		"url":         path,
		"cache":       false,
	}
	if body != "" {
		m["data"] = body
	}

	jquery.Ajax(m).
		Then(func(valueCreated *js.Object) {
		rd := strings.NewReader(valueCreated.String())
		dec := json.NewDecoder(rd)
		if err := dec.Decode(output); err != nil {
			go func() {
				errChan <- AjaxError{418, err.Error()}
			}()
			return
		}
		go func() {
			contentChan <- output
		}()
	}).
		Fail(func(p1 *js.Object) {
		go func() {
			errChan <- AjaxError{p1.Get("status").Int(), p1.Get("responseText").String()}
		}()
	})

	return nil
}

//
// HELPERS
//

func typeToUrlName(i interface{}) string {
	name, ok := i.(string)
	if !ok {
		name = fmt.Sprintf("%T", i)
	}
	pair := strings.Split(name, ".")
	if len(pair) != 2 {
		panic(fmt.Sprintf("unable to understand type name: %s", name))
	}
	return strings.ToLower(pair[1])
}

func encodeBody(i interface{}) (string, error) {
	//encode body
	var w bytes.Buffer
	enc := json.NewEncoder(&w)
	err := enc.Encode(i)
	if err != nil {
		return "", fmt.Errorf("error encoding body: %v ", err)
	}
	return w.String(), nil
}

func isPointerToStructOrPanic(i interface{}) reflect.Type {
	t := reflect.TypeOf(i)
	if t.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to struct but got %T", i))
	}
	if t.Elem().Kind() != reflect.Struct {
		panic(fmt.Sprintf("expected ptr to struct but got ptr to %v", t.Elem().Kind()))
	}
	return t
}

func isPointerToSliceOfPointerToStructOrPanic(i interface{}) reflect.Type {
	t := reflect.TypeOf(i)
	if t.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to slice of ptr to struct but got %T", i))
	}
	if t.Elem().Kind() != reflect.Slice {
		panic(fmt.Sprintf("expected ptr to SLICE of ptr to struct but got ptr to %v", t.Elem().Kind()))
	}
	if t.Elem().Elem().Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to slice of PTR to struct but got ptr to slice of %v", t.Elem().Elem().Kind()))
	}
	if t.Elem().Elem().Elem().Kind() != reflect.Struct {
		panic(fmt.Sprintf("expected ptr to slice of ptr to STRUCT but got ptr to slice of ptr to %v", t.Elem().Elem().Elem().Kind()))
	}
	return t
}

func putPostDel(ptrToStruct interface{}, path string, method string, sendBody bool) (chan interface{}, chan AjaxError) {
	t := isPointerToStructOrPanic(ptrToStruct)
	output := reflect.New(t.Elem())
	var body string

	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)

	if sendBody {
		var err error
		body, err = encodeBody(ptrToStruct)
		if err != nil {
			go func() {
				errCh <- AjaxError{420, err.Error()}
			}()
			return contentCh, errCh
		}
	}
	ajaxRawChannels(output.Interface(), body, contentCh, errCh, method, path)
	return contentCh, errCh
}

func getFieldName(f reflect.StructField) string {
	name := f.Tag.Get("json")
	jsonPreferred := ""
	if name != "" {
		parts := strings.Split(name, ",")
		for _, part := range parts {
			if part == "-" {
				return "-"
			}
			if part == "omitempty" {
				continue
			}
			jsonPreferred = part
		}
	}
	if jsonPreferred != "" {
		return jsonPreferred
	}
	return f.Name
}

//
// DEPRECATED
//

//UnpackJson has been deprecated in favor of the Ajax methods.  This method
//is a naive json unpacker that uses reflection on the go struct to convert
//javascript values.  It cannot handle arbitrary types of fields, cannot handle
//nested structures, nor can it handle the UnmarshalJson interface.
func UnpackJson(ptrToStruct interface{}, jsonBlob *js.Object) error {
	t := reflect.TypeOf(ptrToStruct)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("expected pointer to struct, but got %v", t.Kind())
	}
	elem := t.Elem()
	if elem.Kind() != reflect.Struct {
		return fmt.Errorf("expected pointer to struct, but got pointer to  %v", elem.Kind())
	}
	v := reflect.ValueOf(ptrToStruct)
	v = v.Elem()
	for i := 0; i < elem.NumField(); i++ {
		f := v.Field(i)
		fn := getFieldName(elem.Field(i))
		if fn == "-" || jsonBlob.Get(fn) == js.Undefined || jsonBlob.Get(fn) == nil {
			continue
		}

		//
		// Time is really useful
		//
		if f.Type().Name() == "Time" && f.Type().PkgPath() == "time" {
			str := jsonBlob.Get(fn).String()
			//2015-01-17T17:48:30.346218Z
			//2006-01-02T15:04:05.999999999Z
			t, err := time.Parse(time.RFC3339Nano, str)
			if err != nil {
				print("warning: could not convert string", str, ":", err)
			} else {
				f.Set(reflect.ValueOf(t))
			}
			continue
		}
		switch f.Type().Kind() {
		case reflect.Int64:
			f.SetInt(jsonBlob.Get(fn).Int64())
		case reflect.Int:
			f.SetInt(int64(jsonBlob.Get(fn).Int()))
		case reflect.String:
			f.SetString(jsonBlob.Get(fn).String())
		case reflect.Float64:
			f.SetFloat(jsonBlob.Get(fn).Float())
		case reflect.Bool:
			f.SetBool(jsonBlob.Get(fn).Bool())
		default:
			//print("warning: %s", fn, " has a type other than int64, string, float64 or bool")
		}
	}
	return nil
}
<commit_msg>improve error messages on server is down<commit_after>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/jquery"
)

// AjaxError is returned on the error channel after a call to an Ajax method.
type AjaxError struct {
	StatusCode int
	Message    string
}

//AjaxPut behaves indentically to AjaxPost other than using the method PUT.
func AjaxPut(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "PUT", true)
}

//AjaxDelete behaves indentically to AjaxPost other than using the method DELETE
//and not sending the object to be deleted's contents, just its id. First parameter
//here is just for the type.
func AjaxDelete(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "DELETE", false)
}

//AjaxPost sends an instance of a wire type to the server.  The first argument
//should be a wire type and must be a pointer to a struct or this function
//will panic. The value sent to the server is supplied in the first argument.
//The two returned values are a content channel and an error channel.  If the
//call succeeds, the content channel will be sent a different instance of the
//same type as the first argument. If the result from the server cannot be understood
//as the type of the first argument, the special error code 418 will be sent
//on the error channel.  If we fail to encode the object to be sent, the error
//code 420 will be sent on the error channel and no call to the server is made.
func AjaxPost(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	return putPostDel(ptrToStruct, path, "POST", true)
}

//AjaxIndex retreives a collection of wire types from the server.
//If the first argument is not a pointer to a slice of pointer to struct,
//it will panic.  The first element should be a slice of wire types.
//The returned values are a content channel and an error channel.  The content
//channel will receive the same type as your first argument if anything.  The error
//channel is used for non-200 http responses and the special error code 418
//is used to indicate that the received json from the server could not be successfully
//parsed as the type of the first argument.
func AjaxIndex(ptrToSliceOfPtrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	isPointerToSliceOfPointerToStructOrPanic(ptrToSliceOfPtrToStruct)
	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)
	ajaxRawChannels(ptrToSliceOfPtrToStruct, "", contentCh, errCh, "GET", path)
	return contentCh, errCh
}

//AjaxGet retreives an instance of a wire type from the server and decodes the result as
//Json. If the first argument is not a pointer to a struct, it will panic.
//The first argument should be a wire type that you expect to receive in the success
//case.  The returned values are a content channel and an error channel.  The content
//channel will receive the same type as your first argument if anything.  The error
//channel is used for non-200 http responses and the special error code 418
//is used to indicate that the received json from the server could not be successfully
//parsed as the type of the first argument.
func AjaxGet(ptrToStruct interface{}, path string) (chan interface{}, chan AjaxError) {
	isPointerToStructOrPanic(ptrToStruct)
	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)
	ajaxRawChannels(ptrToStruct, "", contentCh, errCh, "GET", path)
	return contentCh, errCh
}

func ajaxRawChannels(output interface{}, body string, contentChan chan interface{}, errChan chan AjaxError,
	method string, path string) error {

	m := map[string]interface{}{
		"contentType": "application/json",
		"dataType":    "text",
		"type":        method,
		"url":         path,
		"cache":       false,
	}
	if body != "" {
		m["data"] = body
	}

	jquery.Ajax(m).
		Then(func(valueCreated *js.Object) {
		rd := strings.NewReader(valueCreated.String())
		dec := json.NewDecoder(rd)
		if err := dec.Decode(output); err != nil {
			go func() {
				errChan <- AjaxError{418, err.Error()}
			}()
			return
		}
		go func() {
			contentChan <- output
		}()
	}).
		Fail(func(p1 *js.Object) {
		go func() {
			ajaxerr := AjaxError{p1.Get("status").Int(), p1.Get("responseText").String()}
			if p1.Get("status").Int() == 0 {
				ajaxerr.StatusCode = 0
				ajaxerr.Message = "Server not reachable"
			}
			errChan <- ajaxerr
		}()
	})

	return nil
}

//
// HELPERS
//

func typeToUrlName(i interface{}) string {
	name, ok := i.(string)
	if !ok {
		name = fmt.Sprintf("%T", i)
	}
	pair := strings.Split(name, ".")
	if len(pair) != 2 {
		panic(fmt.Sprintf("unable to understand type name: %s", name))
	}
	return strings.ToLower(pair[1])
}

func encodeBody(i interface{}) (string, error) {
	//encode body
	var w bytes.Buffer
	enc := json.NewEncoder(&w)
	err := enc.Encode(i)
	if err != nil {
		return "", fmt.Errorf("error encoding body: %v ", err)
	}
	return w.String(), nil
}

func isPointerToStructOrPanic(i interface{}) reflect.Type {
	t := reflect.TypeOf(i)
	if t.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to struct but got %T", i))
	}
	if t.Elem().Kind() != reflect.Struct {
		panic(fmt.Sprintf("expected ptr to struct but got ptr to %v", t.Elem().Kind()))
	}
	return t
}

func isPointerToSliceOfPointerToStructOrPanic(i interface{}) reflect.Type {
	t := reflect.TypeOf(i)
	if t.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to slice of ptr to struct but got %T", i))
	}
	if t.Elem().Kind() != reflect.Slice {
		panic(fmt.Sprintf("expected ptr to SLICE of ptr to struct but got ptr to %v", t.Elem().Kind()))
	}
	if t.Elem().Elem().Kind() != reflect.Ptr {
		panic(fmt.Sprintf("expected ptr to slice of PTR to struct but got ptr to slice of %v", t.Elem().Elem().Kind()))
	}
	if t.Elem().Elem().Elem().Kind() != reflect.Struct {
		panic(fmt.Sprintf("expected ptr to slice of ptr to STRUCT but got ptr to slice of ptr to %v", t.Elem().Elem().Elem().Kind()))
	}
	return t
}

func putPostDel(ptrToStruct interface{}, path string, method string, sendBody bool) (chan interface{}, chan AjaxError) {
	t := isPointerToStructOrPanic(ptrToStruct)
	output := reflect.New(t.Elem())
	var body string

	contentCh := make(chan interface{})
	errCh := make(chan AjaxError)

	if sendBody {
		var err error
		body, err = encodeBody(ptrToStruct)
		if err != nil {
			go func() {
				errCh <- AjaxError{420, err.Error()}
			}()
			return contentCh, errCh
		}
	}
	ajaxRawChannels(output.Interface(), body, contentCh, errCh, method, path)
	return contentCh, errCh
}

func getFieldName(f reflect.StructField) string {
	name := f.Tag.Get("json")
	jsonPreferred := ""
	if name != "" {
		parts := strings.Split(name, ",")
		for _, part := range parts {
			if part == "-" {
				return "-"
			}
			if part == "omitempty" {
				continue
			}
			jsonPreferred = part
		}
	}
	if jsonPreferred != "" {
		return jsonPreferred
	}
	return f.Name
}

//
// DEPRECATED
//

//UnpackJson has been deprecated in favor of the Ajax methods.  This method
//is a naive json unpacker that uses reflection on the go struct to convert
//javascript values.  It cannot handle arbitrary types of fields, cannot handle
//nested structures, nor can it handle the UnmarshalJson interface.
func UnpackJson(ptrToStruct interface{}, jsonBlob *js.Object) error {
	t := reflect.TypeOf(ptrToStruct)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("expected pointer to struct, but got %v", t.Kind())
	}
	elem := t.Elem()
	if elem.Kind() != reflect.Struct {
		return fmt.Errorf("expected pointer to struct, but got pointer to  %v", elem.Kind())
	}
	v := reflect.ValueOf(ptrToStruct)
	v = v.Elem()
	for i := 0; i < elem.NumField(); i++ {
		f := v.Field(i)
		fn := getFieldName(elem.Field(i))
		if fn == "-" || jsonBlob.Get(fn) == js.Undefined || jsonBlob.Get(fn) == nil {
			continue
		}

		//
		// Time is really useful
		//
		if f.Type().Name() == "Time" && f.Type().PkgPath() == "time" {
			str := jsonBlob.Get(fn).String()
			//2015-01-17T17:48:30.346218Z
			//2006-01-02T15:04:05.999999999Z
			t, err := time.Parse(time.RFC3339Nano, str)
			if err != nil {
				print("warning: could not convert string", str, ":", err)
			} else {
				f.Set(reflect.ValueOf(t))
			}
			continue
		}
		switch f.Type().Kind() {
		case reflect.Int64:
			f.SetInt(jsonBlob.Get(fn).Int64())
		case reflect.Int:
			f.SetInt(int64(jsonBlob.Get(fn).Int()))
		case reflect.String:
			f.SetString(jsonBlob.Get(fn).String())
		case reflect.Float64:
			f.SetFloat(jsonBlob.Get(fn).Float())
		case reflect.Bool:
			f.SetBool(jsonBlob.Get(fn).Bool())
		default:
			//print("warning: %s", fn, " has a type other than int64, string, float64 or bool")
		}
	}
	return nil
}
<|endoftext|>